
Las paqueterías a usar en este documento:
library("easypackages")
packages("tidyverse","fpp3", "tsibble", "feasts","fable")
Un flujo de trabajo limpio de pronóstico
El flujo de trabajo, cuando se va a realizar un proceso, se puede dividir en pasos.
- Preparación de los datos (limpieza)
- Gráfica de los datos (visualización)
- Definición del modelo (especificación)
- Entrenamiento del modelo (estimación)
- Revisar el desempeño del modelo (evaluación)
- Producir pronósticos
1. Preparación de los datos (limpieza)
Esto siempre es lo primero que se hace y puede ir desde simplemente cargar los datos en R, hasta limpiezas más detalladas, como identificar valores omitidos, NA, filtrado de la serie, etc. Para esto, utilizamos varias funciones de las paqueterías tsibble y tidyverse, que simplifican mucho estas tareas.
2. Gráfica de los datos (visualización)
Continuaremos trabajando con los datos de global_economy para ejemplificar esto.
global_economy %>%
filter(Country == "Sweden") %>%
autoplot(GDP) +
ggtitle("PIB de Suecia") + ylab("$US billions")

3. Definición del modelo (especificación)
Antes de ajustar un modelo a los datos, se debe describir el modelo. Existen muchos tipos de modelos de pronóstico distintos, y es muy importante que escojamos el más apropiado, para obtener buenos pronósticos.
Los modelos en R, como vimos con la regresión lineal (lm(y ~ x1 + x2 + ... + xn)), se especifican en formato de fórmula ( y ~ x), siendo la y la variable dependiente (o variable a explicar), y la o las x las variables independientes (o variables explicativas, regresoras, etc.).
Podemos tomar, p. ej., un modelo lineal de series de tiempo, TSLM, modela los datos que se le incluyan mediante una tendencia lineal.
4. Entrenamiento del modelo (estimación)
Una vez que se especificó el modelo, lo que sigue es entrenar al modelo.
Entrenar un modelo significa pasarle los datos para que, estadísticamente, encuentre los parámetros que realizan el mejor ajuste posible.
Siguiendo con el ejemplo del modelo lineal:
fit <- global_economy %>%
model(Modelo_tendencia = TSLM(GDP ~ trend()))
Con esto se ajustó un modelo lineal y el objeto resultante es un mable (model table).
# A mable: 263 x 2
# Key: Country [263]
Country Modelo_tendencia
<fct> <model>
1 Afghanistan <TSLM>
2 Albania <TSLM>
3 Algeria <TSLM>
4 American Samoa <TSLM>
5 Andorra <TSLM>
6 Angola <TSLM>
7 Antigua and Barbuda <TSLM>
8 Arab World <TSLM>
9 Argentina <TSLM>
10 Armenia <TSLM>
# ... with 253 more rows
5. Revisar el desempeño del modelo (evaluación)
Ya teniendo el modelo entrenado, debemos revisar el performance en los datos reales. Esto es, ¿qué tan bien se ajusta el modelo a los datos?, en caso de estar indecisos entre varios modelos, ¿cuál escogemos como el mejor y por qué?
6. Producir pronósticos
Cuando ya evaluamos que el modelo ajustado se encuentra dentro de los parámetros deseados, podemos proceder a realizar los pronósticos. En .R, podemos usar el comando forecast(), en el cual debemos especificar el número de periodos a pronosticar. Por ejemplo, par pronosticar los siguientes 12 meses, escribiríamos h = 12. También podemos usar lenguaje ordinario (en inglés), h = "1 year".
fcst <- fit %>% forecast(h = "3 years")
fcst
# A fable: 789 x 5 [1Y]
# Key: Country, .model [263]
Country .model Year GDP .mean
<fct> <chr> <dbl> <dist> <dbl>
1 Afghani~ Modelo~ 2018 N(1.6e+10, 1.3e+19) 1.62e10
2 Afghani~ Modelo~ 2019 N(1.7e+10, 1.3e+19) 1.65e10
3 Afghani~ Modelo~ 2020 N(1.7e+10, 1.3e+19) 1.68e10
4 Albania Modelo~ 2018 N(1.4e+10, 3.9e+18) 1.37e10
5 Albania Modelo~ 2019 N(1.4e+10, 3.9e+18) 1.42e10
6 Albania Modelo~ 2020 N(1.5e+10, 3.9e+18) 1.46e10
7 Algeria Modelo~ 2018 N(1.6e+11, 9.4e+20) 1.58e11
8 Algeria Modelo~ 2019 N(1.6e+11, 9.4e+20) 1.61e11
9 Algeria Modelo~ 2020 N(1.6e+11, 9.4e+20) 1.64e11
10 America~ Modelo~ 2018 N(6.8e+08, 1.7e+15) 6.82e 8
# ... with 779 more rows
El resultado es una fable (forecasting table) o tabla de pronósticos. El pronóstico se puede graficar fácilmente junto con los datos reales, usando autoplot().
fcst %>%
filter(Country=="Sweden") %>%
autoplot(global_economy) +
ggtitle("PIB de Suecia") + ylab("$US billions")

Métodos sencillos de pronóstico
Utilizaremos de benchmark a lo largo del curso estos métodos básicos de pronóstico. En ocasiones, a pesar de su sencillez, pueden llegar a ser muy útiles.
Utilizaremos los datos de producción de ladrillos para esta sección.
bricks <- aus_production %>% filter_index("1970" ~ "2004")
bricks
bricks %>% autoplot(Bricks)

Método ingenuo (Naïve method)
Aquí, lo que se hace es que se toma el último valor como el pronóstico para todos los valores futuros.
\[
\hat{y}_{T+h | T}=y_{T}
\] Dado que un pronóstico ingenuo es óptimo cuando se tienen datos que siguen una caminata aleatoria, a estos pronósticos se les conoce como pronósticos de caminata aleatoria.
bricks %>% model(NAIVE(Bricks),
RW(Bricks)) # hace exactamente lo mismo que NAIVE()
# A mable: 1 x 2
`NAIVE(Bricks)` `RW(Bricks)`
<model> <model>
1 <NAIVE> <NAIVE>
tidyquant::tq_get("AAPL") %>%
timetk::plot_time_series(.date_var = date,.value = close, .smooth = FALSE)
Método ingenuo estacional (seasonal Naïve)
Un método similar es el ingenuo estacional. Lo que cambia con el anterior es que se agrega un componente para lidiar con datos altamente estacionales.
\[
\hat{y}_{T+h | T}=y_{T+h-m(k+1)}
\]
bricks %>% model(SNAIVE(Bricks))
# A mable: 1 x 1
`SNAIVE(Bricks)`
<model>
1 <SNAIVE>
Método del drift (deriva)
Este método es una variación del método ingenuo, que permite que el pronóstico aumente o disminuya en el tiempo. El aumento del cambio es el cambio promedio en los datos históricos.
\[
\hat{y}_{T+h | T}=y_{T}+\frac{h}{T-1} \sum_{t=2}^{T}\left(y_{t}-y_{t-1}\right)=y_{T}+h\left(\frac{y_{T}-y_{1}}{T-1}\right)
\]
bricks %>% model(
RW(Bricks ~ drift())
)
# A mable: 1 x 1
`RW(Bricks ~ drift())`
<model>
1 <RW w/ drift>
Esto es lo mismo que trazar una línea recta que conecte el primer y último punto en los datos históricos y continuar la recta hacia adelante.
# Set training data from 1992 to 2006
train <- aus_production %>% filter_index("1992 Q1" ~ "2006 Q4")
# Fit the models
beer_fit <- train %>%
model(
Mean = MEAN(Beer),
`Naïve` = NAIVE(Beer),
`Seasonal naïve` = SNAIVE(Beer),
Drift = RW(Beer ~ drift())
)
# Generate forecasts for 14 quarters
beer_fc <- beer_fit %>% forecast(h=14)
# Plot forecasts against actual values
beer_fc %>%
autoplot(train, level = NULL) +
autolayer(filter_index(aus_production, "2007 Q1" ~ .),
color = "black") +
ggtitle("Forecasts for quarterly beer production") +
xlab("Year") + ylab("Megalitres") +
guides(colour=guide_legend(title="Forecast")) +
geom_vline(xintercept = as.Date("2007-01-01"), color = "firebrick",
linetype = "dashed") +
annotate("label", x = c(as.Date("2003-01-01"),as.Date("2009-01-01")),
y = 550, label = c("Train set", "Test set"),
color = c("black","blue"))
Plot variable not specified, automatically selected `.vars = Beer`

beer_fc %>%
filter(.model == "Seasonal naïve") %>%
autoplot(train) +
autolayer(filter_index(aus_production, "2007 Q1" ~ .),
color = "black") +
ggtitle("Seasonal naïve forecast for quarterly beer production") +
xlab("Year") + ylab("Megalitres") +
guides(colour=guide_legend(title="Forecast")) +
geom_vline(xintercept = as.Date("2007-01-01"), color = "firebrick",
linetype = "dashed") +
annotate("label", x = c(as.Date("2003-01-01"),as.Date("2009-01-01")),
y = 550, label = c("Train set", "Test set"),
color = c("black","blue"))
Plot variable not specified, automatically selected `.vars = Beer`

Otro ejemplo:
# Re-index based on trading days
google_stock <- gafa_stock %>%
filter(Symbol == "GOOG") %>%
mutate(day = row_number()) %>%
update_tsibble(index = day, regular = TRUE)
# Filter the year of interest
google_2015 <- google_stock %>% filter(year(Date) == 2015)
# Fit the models
google_fit <- google_2015 %>%
model(
Mean = MEAN(Close),
`Naïve` = NAIVE(Close),
Drift = NAIVE(Close ~ drift())
)
# Produce forecasts for the 19 trading days in January 2015
google_fc <- google_fit %>% forecast(h = 19)
# A better way using a tsibble to determine the forecast horizons
google_jan_2016 <- google_stock %>%
filter(yearmonth(Date) == yearmonth("2016 Jan"))
google_fc <- google_fit %>% forecast(google_jan_2016)
# Plot the forecasts
google_fc %>%
autoplot(google_2015, level = NULL) +
autolayer(google_jan_2016, Close, color='black') +
ggtitle("Google stock (daily ending 31 Dec 2015)") +
xlab("Day") + ylab("Closing Price (US$)") +
guides(colour=guide_legend(title="Forecast"))

Valores ajustados (fitted) y residuales
Cada observación en una serie de tiempo puede ser pronosticada utilizando los datos históricos previos. A estos se les conoce como valores ajustados (o fitted), \(\hat{y}_t\).
Los residuales en un modelo de series de tiempo es la información que el modelo no logró capturar. Esto es, es la diferencia entre los valores reales y los valores ajustados.
\[
e_{t}=y_{t}-\hat{y}_{t}
\]
En R, podemos obtener los valores ajustados y los residuales con la función augment(). Recordando, habíamos ajustado tres modelos distintos, que guardamos en la variable beer_fit.
Es muy importante analizar los residuos para determinar si nuestros modelos están bien ajustados. Si logramos detectar patrones en los residuales, puede ser indicio de que el modelo puede mejorarse.
Diagnóstico de residuales
Un buen modelo de pronóstico va a producir residuales con las siguientes características:
No están autocorrelacionados. Si se detectan correlaciones entre residuos, todavía hay información útil que se debe modelar.
La media de los residuos es cero. Si la media es distinta de cero, entonces el pronóstico está sesgado.
Nota: El hecho de que un pronóstico cumpla esto, no quiere decir que sea el mejor pronóstico que podamos hacer. Próximamente revisaremos qué otras medidas podemos evaluar para determinar cuál es el mejor pronóstico.
Existen dos características adicionales que son útiles, mas no necesarias, para los residuos de un pronóstico:
Los residuos tienen una varianza constante.
Los residuos se distribuyen de manera normal.
Las transformaciones de Box-Cox pueden ayudar, en algunos casos a lograr cumplir estas características.
Continuemos con el ejemplo del pronóstico del precio de la acción de Google. En muchas ocasiones, el mejor pronóstico para los precios de mercados bursátiles e índices suele ser el realiado mediante el método Naïve.
google_2015 %>% autoplot(Close) +
xlab("Day") + ylab("Closing Price (US$)") +
ggtitle("Google Stock in 2015")

Aplicaremos solo el método Naïve para pronosticar el precio futuro de la acción de Google.
aug <- google_2015 %>%
model(NAIVE(Close)) %>%
augment()
aug
aug %>% pull(.resid) %>% mean(na.rm = TRUE)
[1] 0.9439931
aug %>% autoplot(.resid) + xlab("Día") + ylab("") +
ggtitle("Residuales del método naïve")

De la gráfica de los residuales podemos observar que la media parece estar muy cercana al cero y que la variación parece invariante en el tiempo, a excepción de un outlier.
aug %>%
ggplot(aes(x = .resid)) +
geom_histogram() +
ggtitle("Histograma de los residuales")

Del histograma, podemos ver que los residuales parecen distribuirse como una normal, pero con una cola más grande.
aug %>% ACF(.resid) %>% autoplot() + ggtitle("ACF of residuals")

La función de autocorrelación muestra que los residuos no están autocorrelacionados.
Así, el método naïve parece generar pronósticos que capturan toda la información relevante de la serie y, por lo tanto, que satisfacen todas las características necesarias. Por consecuencia, los pronósticos derivados de este método pueden ser bastante buenos, pero los intervalos de predicción pudieran ser imprecisos.
Podemos obtener estas mismas gráficas con un solo comando, gg_tsresiduals().
google_2015 %>%
model(NAIVE(Close)) %>%
gg_tsresiduals() +
ggtitle("Diagnóstico de residuales para el modelo Naïve")

Como vimos antes, el método Naïve es óptimo para este tipo de series. ¿Qué hubiera sucedido si ajustáramos otro modelo? Probemos con el método de la media:
google_2015 %>%
model(MEAN(Close)) %>%
gg_tsresiduals() +
ggtitle("Diagnóstico de residuales para el modelo de Media")

Observamos que estas gráficas tienen un comportamiento muy distinto al Naïve:
En la gráfica de los residuales vemos que se distingue claramente un patrón. De hecho, es el mismo patrón exactamente que siguen los datos originales, restándoles su media.
La función de autocorrelación tiene un comportamiento típico de una caminata aleatoria. Por lo tanto, las autocorrelaciones son significativas.
El histograma de los residuos muestra claramente que no se distribuyen de manera normal.
Tests de Portmanteau de autocorrelación
Para analizar de manera más formal la presencia o ausencia de autocorrelación en los residuos, podemos realizar estas pruebas estadísticas para determinar si las primeras \(h\) autocorrelaciones son significativamente distintas de cero o no.
Test de Box-Pierce
\[
Q=T \sum_{k=1}^{h} r_{k}^{2}
\] En este test, \(h\) es el rezago máximo a considerar y \(T\) es la cantidad de observaciones en la muestra.
Si cada \(r_{k}^{2}\) es pequeña, entonces \(Q\) será pequeña. Se sugiere utilizar \(h = 10\) para datos no estacionales y \(h = 2m\) para datos estacionales (donde \(m\) es el periodo estacional). Sin embargo, la prueba no es tan buena cuando \(h\) es grande, relativamente (o sea, cuando \(h\) es mayor a \(T/5\)). En esos casos, es mejor utilizar \(h = T/5\).
Test de Ljung-Box
Un test relacionado y que, generalmente, es más preciso es el test de Ljung-Box.
\[
Q^{*}=T(T+2) \sum_{k=1}^{h}(T-k)^{-1} r_{k}^{2}
\] En este caso es igual: valores grandes de \(Q^{*}\) son indicios de que las autocorrelaciones no provienen de ruido blanco.
Las pruebas para el modelo ingenuo:
# lag=h and fitdf=K
aug %>% features(.resid, box_pierce, lag=10, dof=0)
aug %>% features(.resid, ljung_box, lag=10, dof=0)
Para el modelo de la media:
google_2015 %>%
model(MEAN(Close)) %>% augment() %>%
features(.resid, box_pierce, lag = 10, dof = 0)
google_2015 %>%
model(MEAN(Close)) %>% augment() %>%
features(.resid, ljung_box, lag = 10, dof = 0)
En ambas pruebas, el p-value resulta ser muy alto, por lo que no podemos distinguir los residuales del ruido blanco.
Intervalos de predicción
Un intervalo de predicción se puede escribir como
\[\hat{y}_{T+h | T} \pm c \hat{\sigma}_{h}\] donde \(c\) es el porcentaje de cobertura de probabilidad. Normalmente utilizaremos 80% y 95%, pero se puede utilizar cualquier porcentaje.
La utilidad de los intervalos de predicción yace en el hecho de que nuestros pronósticos tienen cierta incertidumbre. Así, entre mayor sea el intervalo, menos preciso será nuestro pronóstico, y vice versa.
Un pronóstico puntual por si solo no sirve de gran cosa. Es necesario acompañarlo de su intervalo de predicción.
Intervalos de predicción de un paso
Cuando se realizan pronósticos de un paso (one-step forecast), la desviación estándar del pronóstico es prácticamente la misma que la desviación estándar de los residuos.
Intervalos de predicción de paso múltiple (multi-step)
Conforme se va aumentando el horizonte de pronostico, el intervalo de predicción tiende a aumentar. Entre más adelante en el tiempo queramos pronosticar, tendremos mayor incertidumbre (no es lo mismo querer predecir el tipo de cambio para mañana, que el de diciembre, p. ej.). Esto es, \(\sigma_h\) incrementa con \(h\). Entonces, requerimos estimaciones de \(\sigma_h\).
Para el caso del one-step forecast, ya vimos que podemos tomar la desviación estándar de los residuos como estimación de \(\sigma_h\). Para el caso multi-step, asumimos que los residuos no están autocorrelacionados y se requieren métodos de cálculo un poco más complejos.
Métodos de referencia
Si \(\hat{\sigma}\) es la desviación estándar de los residuos y \(\hat{\sigma}_{h}\) es la desviación estándar del pronóstico \(h\)-step, podemos calcular para cada método de pronóstico de referencia:
Pronósticos de media: \(\hat{\sigma}_{h}=\hat{\sigma} \sqrt{1+1 / T}\)
Pronósticos naïve: \(\hat{\sigma}_{h}=\hat{\sigma} \sqrt{h}\)
Pronósticos naïve estacionales: \(\hat{\sigma}_{h}=\hat{\sigma} \sqrt{k+1}\), donde \(k\) es la parte entera de \((h-1)/m\).
Pronósticos de drift: \(\hat{\sigma}_{h}=\hat{\sigma} \sqrt{h(1+h / T)}\).
Utilizando la paquetería fable, es muy sencillo obtener pronósticos y sus bandas.
google_2015 %>%
model(NAIVE(Close)) %>%
forecast(h = 10) %>%
hilo()
#> # A tsibble: 10 x 6 [1]
#> # Key: Symbol, .model [1]
#> Symbol .model day Close `80%` `95%`
#> <chr> <chr> <dbl> <dbl> <hilo> <hilo>
#> 1 GOOG NAIVE(Close) 505 759. [744.5, 773.2]80 [736.9, 780.8]95
#> 2 GOOG NAIVE(Close) 506 759. [738.6, 779.2]80 [727.9, 789.9]95
#> 3 GOOG NAIVE(Close) 507 759. [734.0, 783.7]80 [720.9, 796.9]95
#> 4 GOOG NAIVE(Close) 508 759. [730.2, 787.6]80 [715.0, 802.7]95
#> 5 GOOG NAIVE(Close) 509 759. [726.8, 790.9]80 [709.8, 807.9]95
#> 6 GOOG NAIVE(Close) 510 759. [723.8, 794.0]80 [705.2, 812.6]95
#> 7 GOOG NAIVE(Close) 511 759. [720.9, 796.8]80 [700.9, 816.9]95
#> 8 GOOG NAIVE(Close) 512 759. [718.3, 799.4]80 [696.8, 820.9]95
#> 9 GOOG NAIVE(Close) 513 759. [715.9, 801.9]80 [693.1, 824.7]95
#> 10 GOOG NAIVE(Close) 514 759. [713.5, 804.2]80 [689.5, 828.2]95
Esto mismo se puede graficar, como lo hemos hecho anteriormente:
google_2015 %>%
model(NAIVE(Close)) %>%
forecast(h = 10) %>%
autoplot(google_2015)

Intervalos de predicción con residuales bootstrap
Cuando no es razonable asumir normalidad en los residuos, podemos aplicarles bootstraping, ya que esto solo asume la no autocorrelación.
Teníamos que los residuos se calculan \(e_{t}=y_{t}-\hat{y}_{t | t-1}\). Reescribiendo, tenemos que:
\[y_{t}=\hat{y}_{t | t-1}+e_{t}\] y podemos simular la siguiente observación de una serie de tiempo con:
\[y_{T+1}=\hat{y}_{T+1 | T}+e_{T+1}\]
donde \(\hat{y}_{T+1} | T\) es el pronóstico de un periodo (one-step) y \(e_{T+1}\) es el error futuro (que desconocemos). Ya que no están autocorelacionados los errores, y puesto que asumimos que los errores futuros serán similares a los históricos, podemos cambiar \(e_{T+1}\) al hacer un muestreo de los residuos. Podemos realizar el mismo proceso para \(y_{T+2}, y_{T+3}, \dots\).
Si realizamos esto varias veces, obtendremos muchos escenarios futuros posibles. Para ver algunos de ellos, utilizamos generate.
fit <- google_2015 %>%
model(NAIVE(Close))
sim <- fit %>% generate(h = 30, times = 5, bootstrap = TRUE, seed = 123) # only works with dev version
sim
Lo que hicimos fue generar 5 escenarios futuros posibles (times = 5) para los siguientes 30 días de trading (h = 30). Si graficamos esto, tenemos:
google_2015 %>%
ggplot(aes(x = day)) +
geom_line(aes(y = Close), size = 1) +
geom_line(aes(y = .sim, colour = as.factor(.rep)), data = sim, size = 1) +
ggtitle("Google closing stock price") +
guides(col = FALSE)

Con esto, podemos obtener intervalos de predicción, al calcular los percentiles de los escenarios futuros. El resultado se llama intervalo de predicción bootstrapped. Esto se puede lograr fácilmente con forecast.
fc <- fit %>% forecast(h = 30, bootstrap = TRUE)
fc
Graficando:
fc %>% autoplot(google_2015) +
ggtitle("Google closing stock price")

Tarea
- Conseguir datos históricos sobre dos series de tiempo.
- Seguir los pasos del flujo de trabajo de pronóstico.
- Estimar los modelos de referencia (benchmark) que consideren adecuados para su serie.
- Realicen el diagnóstico de residuales e interpreten los resultados.
- Ejecuten un pronóstico (ustedes deciden el horizonte de pronóstico) y definan si utilizar el método bootstrap o no y justifiquen su decisión.
Pronósticos con transformaciones
¿Qué sucede cuando realizamos pronósticos de series a las que les hicimos alguna transformación? Por ejemplo, si los contratan para pronosticar las ventas de cubrebocas en la farmacia y ustedes hubieran realizado una transformación logarítmica de los datos, ¿cómo se realiza el pronóstico?
Cuando se realiza una transformación matemática, el pronóstico se hace con esa serie transformada, y posteriormente tenemos que darle reversa a la transformación (back-transformation), para obtener pronósticos en la escala original de la serie.
La transformación inversa de Box-Cox está dada por:
\[
y_{t}=\left\{\begin{array}{ll}
\exp \left(w_{t}\right) & \text { si } \lambda=0 \\
\left(\lambda w_{t}+1\right)^{1 / \lambda} & \text { en otro caso }
\end{array}\right.
\]
La paquetería fable convenientemente realiza la transformación inversa en automático, cuando se especifica en la definición del modelo.
Ajustes por sesgo
Un problema al realizar transformaciones matemáticas, como Box-Cox es que la estimaciones puntuales re transformadas ya no representan la media de la distribución de predicción, sino que representan ahora la mediana. En muchos casos puede no ser tan grave esto, pero en ocasiones el pronóstico promedio es requerido:
- P. ej., si quieren realizar el pronóstico de la venta de cubrebocas en las farmacias de la ZMG, para, al sumarlos, obtener el pronóstico de las ventas totales de cubrebocas en ZMG. La suma de las medias da como resultado el total, pero la suma de medianas no.
La transformación inversa de la media, para Box-Cox es:
\[
y_{t}=\left\{\begin{array}{ll}
\exp \left(w_{t}\right)\left[1+\frac{\sigma_{h}^{2}}{2}\right] & \text { si } \lambda=0 \\
\left(\lambda w_{t}+1\right)^{1 / \lambda}\left[1+\frac{\sigma_{h}^{2}(1-\lambda)}{2\left(\lambda w_{t}+1\right)^{2}}\right] & \text { en otro caso }
\end{array}\right.
\] donde \(\sigma_{h}^{2}\) es la varianza del pronóstico en el horizonte-\(h\) en la escala transformada. Entre más grande sea la varianza, mayor será la diferencia entre la media y la mediana. A esto se le conoce como la estimación ajustada por sesgo.
Veamos un ejemplo sobre el pronóstico del precio promedio del huevo.
eggs <- as_tsibble(fma::eggs)
Registered S3 method overwritten by 'quantmod':
method from
as.zoo.data.frame zoo
fit <- eggs %>% model(RW(log(value) ~ drift()))
fc <- fit %>% forecast(h=50) %>%
mutate(Forecast = "Bias adjusted")
fc_biased <- fit %>% forecast(h=50, bias_adjust = FALSE) %>%
mutate(Forecast = "Simple back transformation")
The `bias_adjust` argument for forecast() has been deprecated. Please specify the desired point forecasts using `point_forecast`.
Bias adjusted forecasts are forecast means (`point_forecast = 'mean'`), non-adjusted forecasts are medians (`point_forecast = 'median'`)
eggs %>% autoplot(value) +
autolayer(fc_biased, level = 80) +
autolayer(fc, colour = "red", level = NULL)

La línea azul representa el pronóstico sesgado, mientras que la línea roja muestra el pronóstico corregido por el sesgo. fable en automático nos produce pronósticos ajustados por sesgo. Como se ve en el código, para generar pronósticos sesgados, tenemos que especificarlo mediante bias_adjust = FALSE.
Pronósticos con descomposición
La descomposición de series de tiempo puede ser útil para producir pronósticos. Reescribiendo las fórmulas de descomposición aditiva y multiplicativa:
\[y_{t}=\hat{S}_{t}+\hat{A}_{t}\]
donde \(\hat{A}_{t}\) es la serie desestacionalizada ( \(\hat{A}_{t} = \hat{T}_{t} + \hat{R}_{t}\)).
\[y_{t}=\hat{S}_{t}\hat{A}_{t}\]
con \(\hat{A}_{t} = \hat{T}_{t} \hat{R}_{t}\).
Así, el pronóstico se realiza en dos pasos: un pronóstico para el componente estacional, y un pronóstico separado para la serie desestacionalizada. De hecho, el pronóstico del componente estacional es simplemente el método naïve estacional. Para los datos desestacionalizados, podemos utilizar cualquier modelo de pronóstico que veremos más adelante.
us_retail_employment <- us_employment %>%
filter(year(Month) >= 1990, Title == "Retail Trade")
dcmp <- us_retail_employment %>%
model(STL(Employed ~ trend(window = 7), robust=TRUE)) %>%
components() %>%
select(-.model)
dcmp %>%
model(NAIVE(season_adjust)) %>%
forecast() %>%
autoplot(dcmp) + ylab("New orders index") +
ggtitle("Pronóstico naïve de los datos desestacionalizados")

A esta serie, podemos agregarle nuevamente la estacionalidad con la función decomposition_model().
us_retail_employment %>%
model(stlf = decomposition_model(
STL(Employed ~ trend(window = 7), robust = TRUE),
NAIVE(season_adjust)
)) %>%
forecast() %>%
autoplot(us_retail_employment)

Evaluación del desempeño de los pronósticos
Conjuntos de entrenamiento y prueba
Como hemos dicho, es muy importante separar nuestros datos en dos conjuntos: un conjunto de datos de entrenamiento, que son los que se utilizan para estimar el modelo, y un conjunto de datos de prueba, donde se evalúa el desempeño del pronóstico.
El tamaño de la prueba es, generalmente, del 20% del total de datos disponibles, aunque también puede depender del horizonte de pronóstico requerido. La prueba tiene que ser al menos tan grande como el horizonte de pronóstico más largo que se requiera (si se necesitan pronósticos para todo el siguiente año, la prueba tiene que ser del tamaño de al menos de todo el siguiente año).
NOTA: Es importante tener en cuenta lo siguiente:
Un modelo que se ajusta muy bien a los datos de entrenamiento no necesariamente produce los mejores pronósticos.
Podemos llegar a tener un ajuste perfecto del modelo a los datos, si aumentamos la cantidad de parámetros.
Puede darse un efecto de sobre ajuste (mejor conocido como over-fitting) y esto es tan malo como tener un muy mal ajuste.
En alguna literatura o paqueterías pueden encontrar que al conjunto de datos de entrenamiento se les conozca como “in-sample data” y al conjunto de prueba “out-of-sample data”.
Funciones para segmentar las series de tiempo
Hemos visto que podemos utilizar la función filter para filtrar una base de datos o una serie de tiempo.
Por ejemplo, tomando la producción en Australia, podemos filtrar para tener los datos a partir de 1995:
aus_production %>% filter(year(Quarter) >= 1995)
o para obtener los datos de una cierta estación:
aus_production %>% filter(quarter(Quarter) == 1)
Otra función útil para filtrar o segmentar series de tiempo es slice(), que utiliza el índice para filtrar los datos.
aus_production %>%
slice(n()-19:0)
Esto filtra los últimos 20 datos (5 años).
A partir de dplyr 1.0.0, existen algunas variantes de slice() que pueden sernos bastante útiles, como slice_head(), slice_tail(), slice_sample(), slice_max(), etc.
Podemos reescribir el código de arriba para hacerlo más explícito:
aus_production %>%
slice_tail(n = 20)
Podemos usar slice para datos agrupados:
aus_retail %>%
group_by(State, Industry) %>%
slice(1:12)
Otra función es top_n, la cual nos permite obtener las n observaciones más extremas.
gafa_stock %>%
group_by(Symbol) %>%
top_n(1, Close)
Errores de pronóstico
El error de pronóstico es la diferencia entre el valor real ocurrido y el dato pronosticado.
\[e_{T+h}=y_{T+h}-\hat{y}_{T+h | T}\]
Los errores de pronóstico son distintos de los residuales en dos formas:
- Los residuales se calculan con los datos de entrenamiento, mientras que los errores de pronóstico se calculan con los de prueba.
- Los residuos se calculan mediante pronósticos de un paso (one-step), donde los errores de pronóstico pueden ser multi-step.
Hay muchos tipos de cálculo del error general de un modelo de pronóstico.
Errores dependientes de la escala de los datos
Los errores de pronóstico están medidos en la misma escala de los datos originales. Hay ciertos tipos de error que solo se basan en \(e_{T}\), por lo que también dependen de la escala y no pueden ser utilizados para comparar el desempeño con otra serie de tiempo que tenga otras unidades.
Los dos más utilizados en este rubro son el MAE y el RMSE.
\[
\begin{aligned}
&\text { Mean absolute error: } \mathrm{MAE}=\operatorname{mean}\left(\left|e_{t}\right|\right)\\
&\text { Root mean squared error: } \operatorname{RMSE}=\sqrt{\operatorname{mean}\left(e_{t}^{2}\right)}
\end{aligned}
\]
El MAE es muy utilizado debido a su facilidad de cómputo y de interpretación. Un método de pronóstico que minimiza el MAE nos dará pronósticos de la mediana de la distribución. Pronósticos que minimizan el RMSE obtienen pronósticos de la media, por lo que este método también es muy utilizado, a pesar de ser más pesado computacionalmente y complicado de interpretar.
Errores porcentuales
Los errores porcentuales, al ser un porcentaje, no tienen unidades y son utilizados para comparar el desempeño de pronósticos de distintos conjuntos de datos. El más utilizado es el MAPE (error absoluto promedio porcentual). Si definimos al error porcentual como \(p_{t}=100 e_{t} / y_{t}\)
Mean absolute percentage error: MAPE \(=\operatorname{mean}\left(\left|p_{t}\right|\right)\)
La desventaja con estos errores es que se indeterminana o vuelven infinitos con valores de \(y_t = 0\). Para ello, se definió el MAPE simétrico:
\[\operatorname{sMAPE}=\operatorname{mean}\left(200\left|y_{t}-\hat{y}_{t}\right| /\left(y_{t}+\hat{y}_{t}\right)\right)\]
Aunque este método no se recomienda tanto utilizarlo en la práctica.
recent_production <- aus_production %>% filter(year(Quarter) >= 1992)
beer_train <- recent_production %>% filter(year(Quarter) <= 2007)
beer_fit <- beer_train %>%
model(
Mean = MEAN(Beer),
`Naïve` = NAIVE(Beer),
`Seasonal naïve` = SNAIVE(Beer),
Drift = RW(Beer ~ drift())
)
beer_fc <- beer_fit %>%
forecast(h = 10)
beer_fc %>%
autoplot(recent_production, level = NULL) +
xlab("Year") + ylab("Megalitres") +
ggtitle("Forecasts for quarterly beer production") +
guides(colour=guide_legend(title="Forecast"))

beer_fc %>%
accuracy(recent_production)
LS0tDQp0aXRsZTogIkxhcyBiYXNlcyBkZSBsb3MgcHJvbsOzc3RpY29zIg0KYXV0aG9yOiAiUGFibG8gQmVuYXZpZGVzLUhlcnJlcmEiDQpkYXRlOiAyMDIwLTA0LTA2DQpvdXRwdXQ6DQogIGh0bWxfbm90ZWJvb2s6DQogICAgdG9jOiB5ZXMNCiAgICB0b2NfZmxvYXQ6IHllcw0KICAgIHRoZW1lOiB1bml0ZWQNCiAgICBoaWdobGlnaHQ6IHRhbmdvDQogIGdpdGh1Yl9kb2N1bWVudDoNCiAgICB0b2M6IHllcw0KICAgIGRldjoganBlZw0KYWx3YXlzX2FsbG93X2h0bWw6IFRSVUUNCi0tLQ0KDQohW10oLi4vaW1hZ2VzL3RzaWJibGUucG5nKXt3aWR0aD0zMCV9IA0KIVtdKC4uL2ltYWdlcy9mYWJsZS5wbmcpe3dpZHRoPTMwJX0NCiFbXSguLi9pbWFnZXMvZmVhc3RzLnBuZyl7d2lkdGg9MzAlfSANCg0KDQpMYXMgcGFxdWV0ZXLDrWFzIGEgdXNhciBlbiBlc3RlIGRvY3VtZW50bzoNCg0KYGBge3IsIG1lc3NhZ2U9RkFMU0V9DQpsaWJyYXJ5KCJlYXN5cGFja2FnZXMiKQ0KcGFja2FnZXMoInRpZHl2ZXJzZSIsImZwcDMiLCAidHNpYmJsZSIsICJmZWFzdHMiLCJmYWJsZSIpDQpgYGANCg0KDQojIFVuIGZsdWpvIGRlIHRyYWJham8gbGltcGlvIGRlIHByb27Ds3N0aWNvDQoNCkVsIGZsdWpvIGRlIHRyYWJham8sIGN1YW5kbyBzZSB2YSBhIHJlYWxpemFyIHVuIHByb2Nlc28sIHNlIHB1ZWRlIGRpdmlkaXIgZW4gcGFzb3MuDQoNCjEuIFByZXBhcmFjacOzbiBkZSBsb3MgZGF0b3MgKGxpbXBpZXphKQ0KMi4gR3LDoWZpY2EgZGUgbG9zIGRhdG9zICh2aXN1YWxpemFjacOzbikNCjMuIERlZmluaWNpw7NuIGRlbCBtb2RlbG8gKGVzcGVjaWZpY2FjacOzbikNCjQuIEVudHJlbmFtaWVudG8gZGVsIG1vZGVsbyAoZXN0aW1hY2nDs24pDQo1LiBSZXZpc2FyIGVsIGRlc2VtcGXDsW8gZGVsIG1vZGVsbyAoZXZhbHVhY2nDs24pDQo2LiBQcm9kdWNpciBwcm9uw7NzdGljb3MNCg0KIyMgMS4gUHJlcGFyYWNpw7NuIGRlIGxvcyBkYXRvcyAobGltcGllemEpDQoNCkVzdG8gc2llbXByZSBlcyBsbyBwcmltZXJvIHF1ZSBzZSBoYWNlIHkgcHVlZGUgaXIgZGVzZGUgc2ltcGxlbWVudGUgY2FyZ2FyIGxvcyBkYXRvcyBlbiAqKlIqKiwgaGFzdGEgbGltcGllemFzIG3DoXMgZGV0YWxsYWRhcywgY29tbyBpZGVudGlmaWNhciB2YWxvcmVzIG9taXRpZG9zLCBgTkFgLCBmaWx0cmFkbyBkZSBsYSBzZXJpZSwgZXRjLiBQYXJhIGVzdG8sIHV0aWxpemFtb3MgdmFyaWFzIGZ1bmNpb25lcyBkZSBsYXMgcGFxdWV0ZXLDrWFzIGB0c2liYmxlYCB5IGB0aWR5dmVyc2VgLCBxdWUgc2ltcGxpZmljYW4gbXVjaG8gZXN0YXMgdGFyZWFzLg0KDQojIyAyLiBHcsOhZmljYSBkZSBsb3MgZGF0b3MgKHZpc3VhbGl6YWNpw7NuKQ0KDQpDb250aW51YXJlbW9zIHRyYWJhamFuZG8gY29uIGxvcyBkYXRvcyBkZSBgZ2xvYmFsX2Vjb25vbXlgIHBhcmEgZWplbXBsaWZpY2FyIGVzdG8uDQoNCmBgYHtyIHZpc3VhbGl6YWNpw7NufQ0KZ2xvYmFsX2Vjb25vbXkgJT4lDQogIGZpbHRlcihDb3VudHJ5ID09ICJTd2VkZW4iKSAlPiUNCiAgYXV0b3Bsb3QoR0RQKSArDQogICAgZ2d0aXRsZSgiUElCIGRlIFN1ZWNpYSIpICsgeWxhYigiJFVTIGJpbGxpb25zIikNCmBgYA0KDQojIyAzLiBEZWZpbmljacOzbiBkZWwgbW9kZWxvIChlc3BlY2lmaWNhY2nDs24pDQoNCkFudGVzIGRlIGFqdXN0YXIgdW4gbW9kZWxvIGEgbG9zIGRhdG9zLCBzZSBkZWJlICoqZGVzY3JpYmlyKiogZWwgbW9kZWxvLiBFeGlzdGVuIG11Y2hvcyB0aXBvcyBkZSBtb2RlbG9zIGRlIHByb27Ds3N0aWNvIGRpc3RpbnRvcywgeSBlcyBtdXkgaW1wb3J0YW50ZSBxdWUgZXNjb2phbW9zIGVsIG3DoXMgYXByb3BpYWRvLCBwYXJhIG9idGVuZXIgYnVlbm9zIHByb27Ds3N0aWNvcy4NCg0KTG9zIG1vZGVsb3MgZW4gKipSKiosIGNvbW8gdmltb3MgY29uIGxhIHJlZ3Jlc2nDs24gbGluZWFsIChgbG0oeSB+IHgxICsgeDIgKyAuLi4gKyB4bilgKSwgc2UgZXNwZWNpZmljYW4gZW4gZm9ybWF0byBkZSBmw7NybXVsYSAoIGB5IH4geGApLCBzaWVuZG8gbGEgYHlgIGxhIHZhcmlhYmxlIGRlcGVuZGllbnRlIChvIHZhcmlhYmxlIGEgZXhwbGljYXIpLCB5IGxhIG8gbGFzIGB4YCBsYXMgdmFyaWFibGVzIGluZGVwZW5kaWVudGVzIChvIHZhcmlhYmxlcyBleHBsaWNhdGl2YXMsIHJlZ3Jlc29yYXMsIGV0Yy4pLg0KDQpQb2RlbW9zIHRvbWFyLCBwLiBlai4sIHVuIG1vZGVsbyBsaW5lYWwgZGUgc2VyaWVzIGRlIHRpZW1wbywgYFRTTE1gLCBtb2RlbGEgbG9zIGRhdG9zIHF1ZSBzZSBsZSBpbmNsdXlhbiBtZWRpYW50ZSB1bmEgdGVuZGVuY2lhIGxpbmVhbC4NCg0KYGBge3IgZGVmaW5pY2nDs24sIGV2YWw9RkFMU0V9DQpUU0xNKEdEUCAgfiB0cmVuZCgpKQ0KYGBgDQoNCg0KIyMgNC4gRW50cmVuYW1pZW50byBkZWwgbW9kZWxvIChlc3RpbWFjacOzbikNCg0KVW5hIHZleiBxdWUgc2UgZXNwZWNpZmljw7MgZWwgbW9kZWxvLCBsbyBxdWUgc2lndWUgZXMgZW50cmVuYXIgYWwgbW9kZWxvLg0KDQpFbnRyZW5hciB1biBtb2RlbG8gc2lnbmlmaWNhIHBhc2FybGUgbG9zIGRhdG9zIHBhcmEgcXVlLCBlc3RhZMOtc3RpY2FtZW50ZSwgZW5jdWVudHJlIGxvcyBwYXLDoW1ldHJvcyBxdWUgcmVhbGl6YW4gZWwgbWVqb3IgYWp1c3RlIHBvc2libGUuDQoNClNpZ3VpZW5kbyBjb24gZWwgZWplbXBsbyBkZWwgbW9kZWxvIGxpbmVhbDoNCg0KYGBge3IgZW50cmVuYW1pZW50bywgd2FybmluZz1GQUxTRX0NCmZpdCA8LSBnbG9iYWxfZWNvbm9teSAlPiUNCiAgbW9kZWwoTW9kZWxvX3RlbmRlbmNpYSA9IFRTTE0oR0RQIH4gdHJlbmQoKSkpDQpgYGANCg0KQ29uIGVzdG8gc2UgYWp1c3TDsyB1biBtb2RlbG8gbGluZWFsIHkgZWwgb2JqZXRvIHJlc3VsdGFudGUgZXMgdW4gYG1hYmxlYCAobW9kZWwgdGFibGUpLg0KDQpgYGB7ciB0YWJsYSBlbnRyZW5hbWllbnRvLCBwYWdlZC5wcmludD1GQUxTRX0NCmZpdA0KYGBgDQoNCiMjIDUuIFJldmlzYXIgZWwgZGVzZW1wZcOxbyBkZWwgbW9kZWxvIChldmFsdWFjacOzbikNCg0KWWEgdGVuaWVuZG8gZWwgbW9kZWxvIGVudHJlbmFkbywgZGViZW1vcyByZXZpc2FyIGVsICpwZXJmb3JtYW5jZSogZW4gbG9zIGRhdG9zIHJlYWxlcy4gRXN0byBlcywgwr9xdcOpIHRhbiBiaWVuIHNlIGFqdXN0YSBlbCBtb2RlbG8gYSBsb3MgZGF0b3M/LCBlbiBjYXNvIGRlIGVzdGFyIGluZGVjaXNvcyBlbnRyZSB2YXJpb3MgbW9kZWxvcywgwr9jdcOhbCBlc2NvZ2Vtb3MgY29tbyBlbCBtZWpvciB5IHBvciBxdcOpPw0KDQojIyA2LiBQcm9kdWNpciBwcm9uw7NzdGljb3MNCg0KQ3VhbmRvIHlhIGV2YWx1YW1vcyBxdWUgZWwgbW9kZWxvIGFqdXN0YWRvIHNlIGVuY3VlbnRyYSBkZW50cm8gZGUgbG9zIHBhcsOhbWV0cm9zIGRlc2VhZG9zLCBwb2RlbW9zIHByb2NlZGVyIGEgcmVhbGl6YXIgbG9zIHByb27Ds3N0aWNvcy4gRW4gLioqUioqLCBwb2RlbW9zIHVzYXIgZWwgY29tYW5kbyBgZm9yZWNhc3QoKWAsIGVuIGVsIGN1YWwgZGViZW1vcyBlc3BlY2lmaWNhciBlbCBuw7ptZXJvIGRlIHBlcmlvZG9zIGEgcHJvbm9zdGljYXIuIFBvciBlamVtcGxvLCBwYXIgcHJvbm9zdGljYXIgbG9zIHNpZ3VpZW50ZXMgMTIgbWVzZXMsIGVzY3JpYmlyw61hbW9zIGBoID0gMTJgLiBUYW1iacOpbiBwb2RlbW9zIHVzYXIgbGVuZ3VhamUgb3JkaW5hcmlvIChlbiBpbmdsw6lzKSwgYGggPSAiMSB5ZWFyImAuDQoNCmBgYHtyIHByb27Ds3N0aWNvLCB3YXJuaW5nPUZBTFNFLCBwYWdlZC5wcmludD1GQUxTRX0NCmZjc3QgPC0gZml0ICU+JSBmb3JlY2FzdChoID0gIjMgeWVhcnMiKQ0KZmNzdA0KYGBgDQoNCkVsIHJlc3VsdGFkbyBlcyB1bmEgYGZhYmxlYCAoZm9yZWNhc3RpbmcgdGFibGUpIG8gdGFibGEgZGUgcHJvbsOzc3RpY29zLiBFbCBwcm9uw7NzdGljbyBzZSBwdWVkZSBncmFmaWNhciBmw6FjaWxtZW50ZSBqdW50byBjb24gbG9zIGRhdG9zIHJlYWxlcywgdXNhbmRvIGBhdXRvcGxvdCgpYC4NCg0KYGBge3IgZ3LDoWZpY2EgcHJvbsOzc3RpY28sIHdhcm5pbmc9RkFMU0V9DQpmY3N0ICU+JSANCiAgZmlsdGVyKENvdW50cnk9PSJTd2VkZW4iKSAlPiUNCiAgYXV0b3Bsb3QoZ2xvYmFsX2Vjb25vbXkpICsNCiAgICBnZ3RpdGxlKCJQSUIgZGUgU3VlY2lhIikgKyB5bGFiKCIkVVMgYmlsbGlvbnMiKQ0KYGBgDQoNCiMgTcOpdG9kb3Mgc2VuY2lsbG9zIGRlIHByb27Ds3N0aWNvIHsgLnRhYnNldH0NCg0KVXRpbGl6YXJlbW9zIGRlICpiZW5jaG1hcmsqIGEgbG8gbGFyZ28gZGVsIGN1cnNvIGVzdG9zIG3DqXRvZG9zIGLDoXNpY29zIGRlIHByb27Ds3N0aWNvLiBFbiBvY2FzaW9uZXMsIGEgcGVzYXIgZGUgc3Ugc2VuY2lsbGV6LCBwdWVkZW4gbGxlZ2FyIGEgc2VyIG11eSDDunRpbGVzLg0KDQpVdGlsaXphcmVtb3MgbG9zIGRhdG9zIGRlIHByb2R1Y2Npw7NuIGRlIGxhZHJpbGxvcyBwYXJhIGVzdGEgc2VjY2nDs24uDQoNCmBgYHtyIGJyaWNrc30NCmJyaWNrcyA8LSBhdXNfcHJvZHVjdGlvbiAlPiUgZmlsdGVyX2luZGV4KCIxOTcwIiB+ICIyMDA0IikNCmJyaWNrcw0KYnJpY2tzICU+JSBhdXRvcGxvdChCcmlja3MpDQpgYGANCg0KDQoNCiMjIE3DqXRvZG8gZGVsIHByb21lZGlvIChtZWRpYSkNCg0KRW4gZXN0ZSBwcm9uw7NzdGljbywgbGFzIHByZWRpY2Npb25lcyBkZSB0b2RvcyBsb3MgdmFsb3JlcyBmdXR1cm9zIHNvbiBsYSBtZWRpYSBkZSBsb3MgZGF0b3MgaGlzdMOzcmljb3MuDQoNCiQkDQpcaGF0e3l9X3tUK2ggfCBUfT1cYmFye3l9PVxsZWZ0KHlfezF9K1xjZG90cyt5X3tUfVxyaWdodCkgLyBUDQokJA0KDQpgYGB7ciB3YXJuaW5nPUZBTFNFLCBwYWdlZC5wcmludD1GQUxTRX0NCmJyaWNrcyAlPiUgbW9kZWwoTUVBTihCcmlja3MpKQ0KYGBgDQoNCiMjIE3DqXRvZG8gaW5nZW51byAoTmHDr3ZlIG1ldGhvZCkNCg0KQXF1w60sIGxvIHF1ZSBzZSBoYWNlIGVzIHF1ZSBzZSB0b21hIGVsIMO6bHRpbW8gdmFsb3IgY29tbyBlbCBwcm9uw7NzdGljbyBwYXJhIHRvZG9zIGxvcyB2YWxvcmVzIGZ1dHVyb3MuDQoNCiQkDQpcaGF0e3l9X3tUK2ggfCBUfT15X3tUfQ0KJCQNCkRhZG8gcXVlIHVuIHByb27Ds3N0aWNvIGluZ2VudW8gZXMgw7NwdGltbyBjdWFuZG8gc2UgdGllbmVuIGRhdG9zIHF1ZSBzaWd1ZW4gdW5hICpjYW1pbmF0YSBhbGVhdG9yaWEqLCBhIGVzdG9zIHByb27Ds3N0aWNvcyBzZSBsZXMgY29ub2NlIGNvbW8gKipwcm9uw7NzdGljb3MgZGUgY2FtaW5hdGEgYWxlYXRvcmlhKiouDQoNCmBgYHtyIHBhZ2VkLnByaW50PUZBTFNFfQ0KYnJpY2tzICU+JSBtb2RlbChOQUlWRShCcmlja3MpLA0KICAgICAgICAgICAgICAgICBSVyhCcmlja3MpKSAjIGhhY2UgZXhhY3RhbWVudGUgbG8gbWlzbW8gcXVlIE5BSVZFKCkNCmBgYA0KDQpgYGB7cn0NCnRpZHlxdWFudDo6dHFfZ2V0KCJBQVBMIikgJT4lIA0KICB0aW1ldGs6OnBsb3RfdGltZV9zZXJpZXMoLmRhdGVfdmFyID0gZGF0ZSwudmFsdWUgPSBjbG9zZSwgLnNtb290aCA9IEZBTFNFKQ0KYGBgDQoNCiMjIE3DqXRvZG8gaW5nZW51byBlc3RhY2lvbmFsIChzZWFzb25hbCBOYcOvdmUpDQoNClVuIG3DqXRvZG8gc2ltaWxhciBlcyBlbCBpbmdlbnVvIGVzdGFjaW9uYWwuIExvIHF1ZSBjYW1iaWEgY29uIGVsIGFudGVyaW9yIGVzIHF1ZSBzZSBhZ3JlZ2EgdW4gY29tcG9uZW50ZSBwYXJhIGxpZGlhciBjb24gZGF0b3MgYWx0YW1lbnRlIGVzdGFjaW9uYWxlcy4NCg0KJCQNClxoYXR7eX1fe1QraCB8IFR9PXlfe1QraC1tKGsrMSl9DQokJA0KYGBge3IgbWVzc2FnZT1GQUxTRSwgcGFnZWQucHJpbnQ9RkFMU0V9DQpicmlja3MgJT4lIG1vZGVsKFNOQUlWRShCcmlja3MpKQ0KYGBgDQoNCiMjIE3DqXRvZG8gZGVsIGRyaWZ0IChkZXJpdmEpDQoNCkVzdGUgbcOpdG9kbyBlcyB1bmEgdmFyaWFjacOzbiBkZWwgbcOpdG9kbyBpbmdlbnVvLCBxdWUgcGVybWl0ZSBxdWUgZWwgcHJvbsOzc3RpY28gYXVtZW50ZSBvIGRpc21pbnV5YSBlbiBlbCB0aWVtcG8uIEVsIGF1bWVudG8gZGVsIGNhbWJpbyBlcyBlbCBjYW1iaW8gcHJvbWVkaW8gZW4gbG9zIGRhdG9zIGhpc3TDs3JpY29zLg0KDQokJA0KXGhhdHt5fV97VCtoIHwgVH09eV97VH0rXGZyYWN7aH17VC0xfSBcc3VtX3t0PTJ9XntUfVxsZWZ0KHlfe3R9LXlfe3QtMX1ccmlnaHQpPXlfe1R9K2hcbGVmdChcZnJhY3t5X3tUfS15X3sxfX17VC0xfVxyaWdodCkNCiQkDQoNCmBgYHtyIHBhZ2VkLnByaW50PUZBTFNFfQ0KYnJpY2tzICU+JSBtb2RlbCgNCiAgUlcoQnJpY2tzIH4gZHJpZnQoKSkNCikNCmBgYA0KDQpFc3RvIGVzIGxvIG1pc21vIHF1ZSB0cmF6YXIgdW5hIGzDrW5lYSByZWN0YSBxdWUgY29uZWN0ZSBlbCBwcmltZXIgeSDDumx0aW1vIHB1bnRvIGVuIGxvcyBkYXRvcyBoaXN0w7NyaWNvcyB5IGNvbnRpbnVhciBsYSByZWN0YSBoYWNpYSBhZGVsYW50ZS4NCg0KIyB7LnVubGlzdGVkIC51bm51bWJlcmVkfQ0KDQoNCmBgYHtyIGJlZXJfZml0IHkgZmNzdH0NCiMgU2V0IHRyYWluaW5nIGRhdGEgZnJvbSAxOTkyIHRvIDIwMDYNCnRyYWluIDwtIGF1c19wcm9kdWN0aW9uICU+JSBmaWx0ZXJfaW5kZXgoIjE5OTIgUTEiIH4gIjIwMDYgUTQiKQ0KIyBGaXQgdGhlIG1vZGVscw0KYmVlcl9maXQgPC0gdHJhaW4gJT4lDQogIG1vZGVsKA0KICAgIE1lYW4gICAgICAgICAgICAgPSBNRUFOKEJlZXIpLA0KICAgIGBOYcOvdmVgICAgICAgICAgID0gTkFJVkUoQmVlciksDQogICAgYFNlYXNvbmFsIG5hw692ZWAgPSBTTkFJVkUoQmVlciksDQogICAgRHJpZnQgICAgICAgICAgICA9IFJXKEJlZXIgfiBkcmlmdCgpKQ0KICApDQojIEdlbmVyYXRlIGZvcmVjYXN0cyBmb3IgMTQgcXVhcnRlcnMNCmJlZXJfZmMgPC0gYmVlcl9maXQgJT4lIGZvcmVjYXN0KGg9MTQpDQoNCiMgUGxvdCBmb3JlY2FzdHMgYWdhaW5zdCBhY3R1YWwgdmFsdWVzDQpiZWVyX2ZjICU+JQ0KICBhdXRvcGxvdCh0cmFpbiwgbGV2ZWwgPSBOVUxMKSArDQogIGF1dG9sYXllcihmaWx0ZXJfaW5kZXgoYXVzX3Byb2R1Y3Rpb24sICIyMDA3IFExIiB+IC4pLCANCiAgICAgICAgICAgIGNvbG9yID0gImJsYWNrIikgKw0KICBnZ3RpdGxlKCJGb3JlY2FzdHMgZm9yIHF1YXJ0ZXJseSBiZWVyIHByb2R1Y3Rpb24iKSArDQogIHhsYWIoIlllYXIiKSArIHlsYWIoIk1lZ2FsaXRyZXMiKSArDQogIGd1aWRlcyhjb2xvdXI9Z3VpZGVfbGVnZW5kKHRpdGxlPSJGb3JlY2FzdCIpKSArDQogIGdlb21fdmxpbmUoeGludGVyY2VwdCA9IGFzLkRhdGUoIjIwMDctMDEtMDEiKSwgY29sb3IgPSAiZmlyZWJyaWNrIiwNCiAgICAgICAgICAgICBsaW5ldHlwZSA9ICJkYXNoZWQiKSArDQogIGFubm90YXRlKCJsYWJlbCIsIHggPSBjKGFzLkRhdGUoIjIwMDMtMDEtMDEiKSxhcy5EYXRlKCIyMDA5LTAxLTAxIikpLA0KICAgICAgICAgICB5ID0gNTUwLCBsYWJlbCA9IGMoIlRyYWluIHNldCIsICJUZXN0IHNldCIpLA0KICAgICAgICAgICBjb2xvciA9IGMoImJsYWNrIiwiYmx1ZSIpKQ0KYGBgDQoNCmBgYHtyfQ0KYmVlcl9mYyAlPiUNCiAgZmlsdGVyKC5tb2RlbCA9PSAiU2Vhc29uYWwgbmHDr3ZlIikgJT4lIA0KICBhdXRvcGxvdCh0cmFpbikgKw0KICBhdXRvbGF5ZXIoZmlsdGVyX2luZGV4KGF1c19wcm9kdWN0aW9uLCAiMjAwNyBRMSIgfiAuKSwgDQogICAgICAgICAgICBjb2xvciA9ICJibGFjayIpICsNCiAgZ2d0aXRsZSgiU2Vhc29uYWwgbmHDr3ZlIGZvcmVjYXN0IGZvciBxdWFydGVybHkgYmVlciBwcm9kdWN0aW9uIikgKw0KICB4bGFiKCJZZWFyIikgKyB5bGFiKCJNZWdhbGl0cmVzIikgKw0KICBndWlkZXMoY29sb3VyPWd1aWRlX2xlZ2VuZCh0aXRsZT0iRm9yZWNhc3QiKSkgKw0KICBnZW9tX3ZsaW5lKHhpbnRlcmNlcHQgPSBhcy5EYXRlKCIyMDA3LTAxLTAxIiksIGNvbG9yID0gImZpcmVicmljayIsDQogICAgICAgICAgICAgbGluZXR5cGUgPSAiZGFzaGVkIikgKw0KICBhbm5vdGF0ZSgibGFiZWwiLCB4ID0gYyhhcy5EYXRlKCIyMDAzLTAxLTAxIiksYXMuRGF0ZSgiMjAwOS0wMS0wMSIpKSwNCiAgICAgICAgICAgeSA9IDU1MCwgbGFiZWwgPSBjKCJUcmFpbiBzZXQiLCAiVGVzdCBzZXQiKSwNCiAgICAgICAgICAgY29sb3IgPSBjKCJibGFjayIsImJsdWUiKSkNCmBgYA0KDQoNCg0KT3RybyBlamVtcGxvOg0KDQpgYGB7ciBwbG90IGdvb2dsZSBmY3N0c30NCiMgUmUtaW5kZXggYmFzZWQgb24gdHJhZGluZyBkYXlzDQpnb29nbGVfc3RvY2sgPC0gZ2FmYV9zdG9jayAlPiUNCiAgZmlsdGVyKFN5bWJvbCA9PSAiR09PRyIpICU+JQ0KICBtdXRhdGUoZGF5ID0gcm93X251bWJlcigpKSAlPiUNCiAgdXBkYXRlX3RzaWJibGUoaW5kZXggPSBkYXksIHJlZ3VsYXIgPSBUUlVFKQ0KIyBGaWx0ZXIgdGhlIHllYXIgb2YgaW50ZXJlc3QNCmdvb2dsZV8yMDE1IDwtIGdvb2dsZV9zdG9jayAlPiUgZmlsdGVyKHllYXIoRGF0ZSkgPT0gMjAxNSkNCiMgRml0IHRoZSBtb2RlbHMNCmdvb2dsZV9maXQgPC0gZ29vZ2xlXzIwMTUgJT4lDQogIG1vZGVsKA0KICAgIE1lYW4gPSBNRUFOKENsb3NlKSwNCiAgICBgTmHDr3ZlYCA9IE5BSVZFKENsb3NlKSwNCiAgICBEcmlmdCA9IE5BSVZFKENsb3NlIH4gZHJpZnQoKSkNCiAgKQ0KIyBQcm9kdWNlIGZvcmVjYXN0cyBmb3IgdGhlIDE5IHRyYWRpbmcgZGF5cyBpbiBKYW51YXJ5IDIwMTUNCmdvb2dsZV9mYyA8LSBnb29nbGVfZml0ICU+JSBmb3JlY2FzdChoID0gMTkpDQojIEEgYmV0dGVyIHdheSB1c2luZyBhIHRzaWJibGUgdG8gZGV0ZXJtaW5lIHRoZSBmb3JlY2FzdCBob3Jpem9ucw0KZ29vZ2xlX2phbl8yMDE2IDwtIGdvb2dsZV9zdG9jayAlPiUNCiAgZmlsdGVyKHllYXJtb250aChEYXRlKSA9PSB5ZWFybW9udGgoIjIwMTYgSmFuIikpDQpnb29nbGVfZmMgPC0gZ29vZ2xlX2ZpdCAlPiUgZm9yZWNhc3QoZ29vZ2xlX2phbl8yMDE2KQ0KIyBQbG90IHRoZSBmb3JlY2FzdHMNCmdvb2dsZV9mYyAlPiUNCiAgYXV0b3Bsb3QoZ29vZ2xlXzIwMTUsIGxldmVsID0gTlVMTCkgKw0KICAgIGF1dG9sYXllcihnb29nbGVfamFuXzIwMTYsIENsb3NlLCBjb2xvcj0nYmxhY2snKSArDQogICAgZ2d0aXRsZSgiR29vZ2xlIHN0b2NrIChkYWlseSBlbmRpbmcgMzEgRGVjIDIwMTUpIikgKw0KICAgIHhsYWIoIkRheSIpICsgeWxhYigiQ2xvc2luZyBQcmljZSAoVVMkKSIpICsNCiAgICBndWlkZXMoY29sb3VyPWd1aWRlX2xlZ2VuZCh0aXRsZT0iRm9yZWNhc3QiKSkNCmBgYA0KDQojIFZhbG9yZXMgYWp1c3RhZG9zICooZml0dGVkKSogeSByZXNpZHVhbGVzDQoNCg0KQ2FkYSBvYnNlcnZhY2nDs24gZW4gdW5hIHNlcmllIGRlIHRpZW1wbyBwdWVkZSBzZXIgcHJvbm9zdGljYWRhIHV0aWxpemFuZG8gbG9zIGRhdG9zIGhpc3TDs3JpY29zIHByZXZpb3MuIEEgZXN0b3Mgc2UgbGVzIGNvbm9jZSBjb21vIHZhbG9yZXMgYWp1c3RhZG9zIChvICpmaXR0ZWQqKSwgJFxoYXR7eX1fdCQuDQoNCkxvcyByZXNpZHVhbGVzIGVuIHVuIG1vZGVsbyBkZSBzZXJpZXMgZGUgdGllbXBvIGVzIGxhIGluZm9ybWFjacOzbiBxdWUgZWwgbW9kZWxvIG5vIGxvZ3LDsyBjYXB0dXJhci4gRXN0byBlcywgZXMgbGEgZGlmZXJlbmNpYSBlbnRyZSBsb3MgdmFsb3JlcyByZWFsZXMgeSBsb3MgdmFsb3JlcyBhanVzdGFkb3MuDQoNCiQkDQplX3t0fT15X3t0fS1caGF0e3l9X3t0fQ0KJCQNCg0KRW4gKipSKiosIHBvZGVtb3Mgb2J0ZW5lciBsb3MgdmFsb3JlcyBhanVzdGFkb3MgeSBsb3MgcmVzaWR1YWxlcyBjb24gbGEgZnVuY2nDs24gYGF1Z21lbnQoKWAuIFJlY29yZGFuZG8sIGhhYsOtYW1vcyBhanVzdGFkbyB0cmVzIG1vZGVsb3MgZGlzdGludG9zLCBxdWUgZ3VhcmRhbW9zIGVuIGxhIHZhcmlhYmxlIGBiZWVyX2ZpdGAuDQoNCmBgYHtyfQ0KP2F1Z21lbnQNCmBgYA0KDQoNCmBgYHtyIGF1Z21lbnR9DQphdWdtZW50KGJlZXJfZml0KQ0KYGBgDQoNCkVzIG11eSBpbXBvcnRhbnRlIGFuYWxpemFyIGxvcyByZXNpZHVvcyBwYXJhIGRldGVybWluYXIgc2kgbnVlc3Ryb3MgbW9kZWxvcyBlc3TDoW4gYmllbiBhanVzdGFkb3MuIFNpIGxvZ3JhbW9zIGRldGVjdGFyIHBhdHJvbmVzIGVuIGxvcyByZXNpZHVhbGVzLCBwdWVkZSBzZXIgaW5kaWNpbyBkZSBxdWUgZWwgbW9kZWxvIHB1ZWRlIG1lam9yYXJzZS4NCg0KIyBEaWFnbsOzc3RpY28gZGUgcmVzaWR1YWxlcw0KDQpVbiBidWVuIG1vZGVsbyBkZSBwcm9uw7NzdGljbyB2YSBhIHByb2R1Y2lyIHJlc2lkdWFsZXMgY29uIGxhcyBzaWd1aWVudGVzIGNhcmFjdGVyw61zdGljYXM6DQoNCjEuICoqTm8gZXN0w6FuIGF1dG9jb3JyZWxhY2lvbmFkb3MqKi4gU2kgc2UgZGV0ZWN0YW4gY29ycmVsYWNpb25lcyBlbnRyZSByZXNpZHVvcywgdG9kYXbDrWEgaGF5IGluZm9ybWFjacOzbiDDunRpbCBxdWUgc2UgZGViZSBtb2RlbGFyLg0KDQoyLiAqKkxhIG1lZGlhIGRlIGxvcyByZXNpZHVvcyBlcyBjZXJvKiouIFNpIGxhIG1lZGlhIGVzIGRpc3RpbnRhIGRlIGNlcm8sIGVudG9uY2VzIGVsIHByb27Ds3N0aWNvIGVzdMOhIHNlc2dhZG8uDQoNCioqTm90YToqKiBFbCBoZWNobyBkZSBxdWUgdW4gcHJvbsOzc3RpY28gY3VtcGxhIGVzdG8sIG5vIHF1aWVyZSBkZWNpciBxdWUgc2VhIGVsIG1lam9yIHByb27Ds3N0aWNvIHF1ZSBwb2RhbW9zIGhhY2VyLiBQcsOzeGltYW1lbnRlIHJldmlzYXJlbW9zIHF1w6kgb3RyYXMgbWVkaWRhcyBwb2RlbW9zIGV2YWx1YXIgcGFyYSBkZXRlcm1pbmFyIGN1w6FsIGVzIGVsIG1lam9yIHByb27Ds3N0aWNvLg0KDQpFeGlzdGVuIGRvcyBjYXJhY3RlcsOtc3RpY2FzIGFkaWNpb25hbGVzIHF1ZSBzb24gw7p0aWxlcywgbWFzIG5vIG5lY2VzYXJpYXMsIHBhcmEgbG9zIHJlc2lkdW9zIGRlIHVuIHByb27Ds3N0aWNvOg0KDQozLiBMb3MgcmVzaWR1b3MgdGllbmVuIHVuYSB2YXJpYW56YSBjb25zdGFudGUuDQoNCjQuIExvcyByZXNpZHVvcyBzZSBkaXN0cmlidXllbiBkZSBtYW5lcmEgbm9ybWFsLg0KDQpMYXMgdHJhbnNmb3JtYWNpb25lcyBkZSBCb3gtQ294IHB1ZWRlbiBheXVkYXIsIGVuIGFsZ3Vub3MgY2Fzb3MgYSBsb2dyYXIgY3VtcGxpciBlc3RhcyBjYXJhY3RlcsOtc3RpY2FzLg0KDQpDb250aW51ZW1vcyBjb24gZWwgZWplbXBsbyBkZWwgcHJvbsOzc3RpY28gZGVsIHByZWNpbyBkZSBsYSBhY2Npw7NuIGRlIEdvb2dsZS4gRW4gbXVjaGFzIG9jYXNpb25lcywgZWwgbWVqb3IgcHJvbsOzc3RpY28gcGFyYSBsb3MgcHJlY2lvcyBkZSBtZXJjYWRvcyBidXJzw6F0aWxlcyBlIMOtbmRpY2VzIHN1ZWxlIHNlciBlbCByZWFsaWFkbyBtZWRpYW50ZSBlbCBtw6l0b2RvIE5hw692ZS4NCg0KDQpgYGB7ciBnb29nbGUgcGxvdH0NCmdvb2dsZV8yMDE1ICU+JSBhdXRvcGxvdChDbG9zZSkgKw0KICB4bGFiKCJEYXkiKSArIHlsYWIoIkNsb3NpbmcgUHJpY2UgKFVTJCkiKSArDQogIGdndGl0bGUoIkdvb2dsZSBTdG9jayBpbiAyMDE1IikNCmBgYA0KDQpBcGxpY2FyZW1vcyBzb2xvIGVsIG3DqXRvZG8gTmHDr3ZlIHBhcmEgcHJvbm9zdGljYXIgZWwgcHJlY2lvIGZ1dHVybyBkZSBsYSBhY2Npw7NuIGRlIEdvb2dsZS4NCg0KYGBge3IgcmVzaWR1YWxlcyBnb29nbGV9DQphdWcgPC0gZ29vZ2xlXzIwMTUgJT4lIA0KICBtb2RlbChOQUlWRShDbG9zZSkpICU+JSANCiAgYXVnbWVudCgpDQoNCmF1Zw0KDQphdWcgJT4lIHB1bGwoLnJlc2lkKSAlPiUgbWVhbihuYS5ybSA9IFRSVUUpIA0KDQphdWcgJT4lIGF1dG9wbG90KC5yZXNpZCkgKyB4bGFiKCJEw61hIikgKyB5bGFiKCIiKSArDQogIGdndGl0bGUoIlJlc2lkdWFsZXMgZGVsIG3DqXRvZG8gbmHDr3ZlIikNCmBgYA0KDQpEZSBsYSBncsOhZmljYSBkZSBsb3MgcmVzaWR1YWxlcyBwb2RlbW9zIG9ic2VydmFyIHF1ZSBsYSBtZWRpYSBwYXJlY2UgZXN0YXIgbXV5IGNlcmNhbmEgYWwgY2VybyB5IHF1ZSBsYSB2YXJpYWNpw7NuIHBhcmVjZSBpbnZhcmlhbnRlIGVuIGVsIHRpZW1wbywgYSBleGNlcGNpw7NuIGRlIHVuIG91dGxpZXIuDQoNCmBgYHtyIHJlc2lkcyBoaXN0LCB3YXJuaW5nPUZBTFNFfQ0KYXVnICU+JQ0KICBnZ3Bsb3QoYWVzKHggPSAucmVzaWQpKSArDQogIGdlb21faGlzdG9ncmFtKCkgKw0KICBnZ3RpdGxlKCJIaXN0b2dyYW1hIGRlIGxvcyByZXNpZHVhbGVzIikNCmBgYA0KRGVsIGhpc3RvZ3JhbWEsIHBvZGVtb3MgdmVyIHF1ZSBsb3MgcmVzaWR1YWxlcyBwYXJlY2VuIGRpc3RyaWJ1aXJzZSBjb21vIHVuYSBub3JtYWwsIHBlcm8gY29uIHVuYSBjb2xhIG3DoXMgZ3JhbmRlLg0KDQpgYGB7ciBhY2YgcmVzaWRzfQ0KYXVnICU+JSBBQ0YoLnJlc2lkKSAlPiUgYXV0b3Bsb3QoKSArIGdndGl0bGUoIkFDRiBvZiByZXNpZHVhbHMiKQ0KYGBgDQoNCkxhIGZ1bmNpw7NuIGRlIGF1dG9jb3JyZWxhY2nDs24gbXVlc3RyYSBxdWUgbG9zIHJlc2lkdW9zIG5vIGVzdMOhbiBhdXRvY29ycmVsYWNpb25hZG9zLg0KDQpBc8OtLCBlbCBtw6l0b2RvIG5hw692ZSBwYXJlY2UgZ2VuZXJhciBwcm9uw7NzdGljb3MgcXVlIGNhcHR1cmFuIHRvZGEgbGEgaW5mb3JtYWNpw7NuIHJlbGV2YW50ZSBkZSBsYSBzZXJpZSB5LCBwb3IgbG8gdGFudG8sIHF1ZSBzYXRpc2ZhY2VuIHRvZGFzIGxhcyBjYXJhY3RlcsOtc3RpY2FzIG5lY2VzYXJpYXMuIFBvciBjb25zZWN1ZW5jaWEsIGxvcyBwcm9uw7NzdGljb3MgZGVyaXZhZG9zIGRlIGVzdGUgbcOpdG9kbyBwdWVkZW4gc2VyIGJhc3RhbnRlIGJ1ZW5vcywgcGVybyBsb3MgaW50ZXJ2YWxvcyBkZSBwcmVkaWNjacOzbiBwdWRpZXJhbiBzZXIgaW1wcmVjaXNvcy4NCg0KUG9kZW1vcyBvYnRlbmVyIGVzdGFzIG1pc21hcyBncsOhZmljYXMgY29uIHVuIHNvbG8gY29tYW5kbywgYGdnX3RzcmVzaWR1YWxzKClgLg0KDQpgYGB7ciBnZ190c3Jlc2lkdWFscyBuYWl2ZSwgd2FybmluZz1GQUxTRX0NCmdvb2dsZV8yMDE1ICU+JSANCiAgbW9kZWwoTkFJVkUoQ2xvc2UpKSAlPiUgDQogIGdnX3RzcmVzaWR1YWxzKCkgKyANCiAgZ2d0aXRsZSgiRGlhZ27Ds3N0aWNvIGRlIHJlc2lkdWFsZXMgcGFyYSBlbCBtb2RlbG8gTmHDr3ZlIikNCmBgYA0KQ29tbyB2aW1vcyBhbnRlcywgZWwgbcOpdG9kbyBOYcOvdmUgZXMgw7NwdGltbyBwYXJhIGVzdGUgdGlwbyBkZSBzZXJpZXMuIMK/UXXDqSBodWJpZXJhIHN1Y2VkaWRvIHNpIGFqdXN0w6FyYW1vcyBvdHJvIG1vZGVsbz8gUHJvYmVtb3MgY29uIGVsIG3DqXRvZG8gZGUgbGEgbWVkaWE6DQpgYGB7ciBnZ190c3Jlc2lkdWFscyBtZWFuLCB3YXJuaW5nPUZBTFNFfQ0KZ29vZ2xlXzIwMTUgJT4lIA0KICBtb2RlbChNRUFOKENsb3NlKSkgJT4lIA0KICBnZ190c3Jlc2lkdWFscygpICsgDQogIGdndGl0bGUoIkRpYWduw7NzdGljbyBkZSByZXNpZHVhbGVzIHBhcmEgZWwgbW9kZWxvIGRlIE1lZGlhIikNCmBgYA0KDQpPYnNlcnZhbW9zIHF1ZSBlc3RhcyBncsOhZmljYXMgdGllbmVuIHVuIGNvbXBvcnRhbWllbnRvIG11eSBkaXN0aW50byBhbCBOYcOvdmU6DQoNCiogRW4gbGEgZ3LDoWZpY2EgZGUgbG9zIHJlc2lkdWFsZXMgdmVtb3MgcXVlIHNlIGRpc3Rpbmd1ZSBjbGFyYW1lbnRlIHVuIHBhdHLDs24uIERlIGhlY2hvLCBlcyBlbCBtaXNtbyBwYXRyw7NuIGV4YWN0YW1lbnRlIHF1ZSBzaWd1ZW4gbG9zIGRhdG9zIG9yaWdpbmFsZXMsIHJlc3TDoW5kb2xlcyBzdSBtZWRpYS4NCg0KKiBMYSBmdW5jacOzbiBkZSBhdXRvY29ycmVsYWNpw7NuIHRpZW5lIHVuIGNvbXBvcnRhbWllbnRvIHTDrXBpY28gZGUgdW5hIGNhbWluYXRhIGFsZWF0b3JpYS4gUG9yIGxvIHRhbnRvLCBsYXMgYXV0b2NvcnJlbGFjaW9uZXMgc29uIHNpZ25pZmljYXRpdmFzLg0KDQoqIEVsIGhpc3RvZ3JhbWEgZGUgbG9zIHJlc2lkdW9zIG11ZXN0cmEgY2xhcmFtZW50ZSBxdWUgbm8gc2UgZGlzdHJpYnV5ZW4gZGUgbWFuZXJhIG5vcm1hbC4NCg0KDQoNCiMjIyBUZXN0cyBkZSBQb3J0bWFudGVhdSBkZSBhdXRvY29ycmVsYWNpw7NuDQoNClBhcmEgYW5hbGl6YXIgZGUgbWFuZXJhIG3DoXMgZm9ybWFsIGxhIHByZXNlbmNpYSBvIGF1c2VuY2lhIGRlIGF1dG9jb3JyZWxhY2nDs24gZW4gbG9zIHJlc2lkdW9zLCBwb2RlbW9zIHJlYWxpemFyIGVzdGFzIHBydWViYXMgZXN0YWTDrXN0aWNhcyBwYXJhIGRldGVybWluYXIgc2kgbGFzIHByaW1lcmFzICRoJCBhdXRvY29ycmVsYWNpb25lcyBzb24gc2lnbmlmaWNhdGl2YW1lbnRlIGRpc3RpbnRhcyBkZSBjZXJvIG8gbm8uDQoNCiMjIyMgVGVzdCBkZSBCb3gtUGllcmNlDQoNCiQkDQpRPVQgXHN1bV97az0xfV57aH0gcl97a31eezJ9DQokJA0KRW4gZXN0ZSB0ZXN0LCAkaCQgZXMgZWwgcmV6YWdvIG3DoXhpbW8gYSBjb25zaWRlcmFyIHkgJFQkIGVzIGxhIGNhbnRpZGFkIGRlIG9ic2VydmFjaW9uZXMgZW4gbGEgbXVlc3RyYS4NCg0KU2kgY2FkYSAkcl97a31eezJ9JCBlcyBwZXF1ZcOxYSwgZW50b25jZXMgJFEkIHNlcsOhIHBlcXVlw7FhLiBTZSBzdWdpZXJlIHV0aWxpemFyICRoID0gMTAkIHBhcmEgZGF0b3Mgbm8gZXN0YWNpb25hbGVzIHkgJGggPSAybSQgcGFyYSBkYXRvcyBlc3RhY2lvbmFsZXMgKGRvbmRlICRtJCBlcyBlbCBwZXJpb2RvIGVzdGFjaW9uYWwpLiBTaW4gZW1iYXJnbywgbGEgcHJ1ZWJhIG5vIGVzIHRhbiBidWVuYSBjdWFuZG8gJGgkIGVzIGdyYW5kZSwgcmVsYXRpdmFtZW50ZSAobyBzZWEsIGN1YW5kbyAkaCQgZXMgbWF5b3IgYSAkVC81JCkuIEVuIGVzb3MgY2Fzb3MsIGVzIG1lam9yIHV0aWxpemFyICRoID0gVC81JC4NCg0KIyMjIyBUZXN0IGRlIExqdW5nLUJveA0KDQpVbiB0ZXN0IHJlbGFjaW9uYWRvIHkgcXVlLCBnZW5lcmFsbWVudGUsIGVzIG3DoXMgcHJlY2lzbyBlcyBlbCB0ZXN0IGRlIExqdW5nLUJveC4NCg0KJCQNClFeeyp9PVQoVCsyKSBcc3VtX3trPTF9XntofShULWspXnstMX0gcl97a31eezJ9DQokJA0KRW4gZXN0ZSBjYXNvIGVzIGlndWFsOiB2YWxvcmVzIGdyYW5kZXMgZGUgJFFeeyp9JCBzb24gaW5kaWNpb3MgZGUgcXVlIGxhcyBhdXRvY29ycmVsYWNpb25lcyBubyBwcm92aWVuZW4gZGUgcnVpZG8gYmxhbmNvLg0KDQpMYXMgcHJ1ZWJhcyBwYXJhIGVsIG1vZGVsbyBpbmdlbnVvOg0KDQpgYGB7cn0NCiMgbGFnPWggYW5kIGZpdGRmPUsNCmF1ZyAlPiUgZmVhdHVyZXMoLnJlc2lkLCBib3hfcGllcmNlLCBsYWc9MTAsIGRvZj0wKQ0KDQphdWcgJT4lIGZlYXR1cmVzKC5yZXNpZCwgbGp1bmdfYm94LCBsYWc9MTAsIGRvZj0wKQ0KDQpgYGANClBhcmEgZWwgbW9kZWxvIGRlIGxhIG1lZGlhOg0KDQpgYGB7cn0NCmdvb2dsZV8yMDE1ICU+JSANCiAgbW9kZWwoTUVBTihDbG9zZSkpICU+JSBhdWdtZW50KCkgJT4lIA0KICBmZWF0dXJlcygucmVzaWQsIGJveF9waWVyY2UsIGxhZyA9IDEwLCBkb2YgPSAwKQ0KDQpnb29nbGVfMjAxNSAlPiUgDQogIG1vZGVsKE1FQU4oQ2xvc2UpKSAlPiUgYXVnbWVudCgpICU+JSANCiAgZmVhdHVyZXMoLnJlc2lkLCBsanVuZ19ib3gsIGxhZyA9IDEwLCBkb2YgPSAwKQ0KYGBgDQoNCg0KRW4gYW1iYXMgcHJ1ZWJhcywgZWwgcC12YWx1ZSByZXN1bHRhIHNlciBtdXkgYWx0bywgcG9yIGxvIHF1ZSBubyBwb2RlbW9zIGRpc3Rpbmd1aXIgbG9zIHJlc2lkdWFsZXMgZGVsIHJ1aWRvIGJsYW5jby4NCg0KDQojIEludGVydmFsb3MgZGUgcHJlZGljY2nDs24NCg0KVW4gaW50ZXJ2YWxvIGRlIHByZWRpY2Npw7NuIHNlIHB1ZWRlIGVzY3JpYmlyIGNvbW8NCg0KJCRcaGF0e3l9X3tUK2ggfCBUfSBccG0gYyBcaGF0e1xzaWdtYX1fe2h9JCQNCmRvbmRlICRjJCBlcyBlbCBwb3JjZW50YWplIGRlIGNvYmVydHVyYSBkZSBwcm9iYWJpbGlkYWQuIE5vcm1hbG1lbnRlIHV0aWxpemFyZW1vcyA4MCUgeSA5NSUsIHBlcm8gc2UgcHVlZGUgdXRpbGl6YXIgY3VhbHF1aWVyIHBvcmNlbnRhamUuDQoNCkxhIHV0aWxpZGFkIGRlIGxvcyBpbnRlcnZhbG9zIGRlIHByZWRpY2Npw7NuIHlhY2UgZW4gZWwgaGVjaG8gZGUgcXVlIG51ZXN0cm9zIHByb27Ds3N0aWNvcyB0aWVuZW4gY2llcnRhIGluY2VydGlkdW1icmUuIEFzw60sIGVudHJlIG1heW9yIHNlYSBlbCBpbnRlcnZhbG8sIG1lbm9zIHByZWNpc28gc2Vyw6EgbnVlc3RybyBwcm9uw7NzdGljbywgeSB2aWNlIHZlcnNhLg0KDQpVbiBwcm9uw7NzdGljbyBwdW50dWFsIHBvciBzaSBzb2xvIG5vIHNpcnZlIGRlIGdyYW4gY29zYS4gRXMgbmVjZXNhcmlvIGFjb21wYcOxYXJsbyBkZSBzdSBpbnRlcnZhbG8gZGUgcHJlZGljY2nDs24uDQoNCiMjIyBJbnRlcnZhbG9zIGRlIHByZWRpY2Npw7NuIGRlIHVuIHBhc28NCg0KQ3VhbmRvIHNlIHJlYWxpemFuIHByb27Ds3N0aWNvcyBkZSB1biBwYXNvICgqb25lLXN0ZXAgZm9yZWNhc3QqKSwgbGEgZGVzdmlhY2nDs24gZXN0w6FuZGFyIGRlbCBwcm9uw7NzdGljbyBlcyBwcsOhY3RpY2FtZW50ZSBsYSBtaXNtYSBxdWUgbGEgZGVzdmlhY2nDs24gZXN0w6FuZGFyIGRlIGxvcyByZXNpZHVvcy4NCg0KIyMjIEludGVydmFsb3MgZGUgcHJlZGljY2nDs24gZGUgcGFzbyBtw7psdGlwbGUgKCptdWx0aS1zdGVwKikNCg0KQ29uZm9ybWUgc2UgdmEgYXVtZW50YW5kbyBlbCBob3Jpem9udGUgZGUgcHJvbm9zdGljbywgZWwgaW50ZXJ2YWxvIGRlIHByZWRpY2Npw7NuIHRpZW5kZSBhIGF1bWVudGFyLiBFbnRyZSBtw6FzIGFkZWxhbnRlIGVuIGVsIHRpZW1wbyBxdWVyYW1vcyBwcm9ub3N0aWNhciwgdGVuZHJlbW9zIG1heW9yIGluY2VydGlkdW1icmUgKG5vIGVzIGxvIG1pc21vIHF1ZXJlciBwcmVkZWNpciBlbCB0aXBvIGRlIGNhbWJpbyBwYXJhIG1hw7FhbmEsIHF1ZSBlbCBkZSBkaWNpZW1icmUsIHAuIGVqLikuIEVzdG8gZXMsICRcc2lnbWFfaCQgaW5jcmVtZW50YSBjb24gJGgkLiBFbnRvbmNlcywgcmVxdWVyaW1vcyBlc3RpbWFjaW9uZXMgZGUgJFxzaWdtYV9oJC4NCg0KUGFyYSBlbCBjYXNvIGRlbCBvbmUtc3RlcCBmb3JlY2FzdCwgeWEgdmltb3MgcXVlIHBvZGVtb3MgdG9tYXIgbGEgZGVzdmlhY2nDs24gZXN0w6FuZGFyIGRlIGxvcyByZXNpZHVvcyBjb21vIGVzdGltYWNpw7NuIGRlICRcc2lnbWFfaCQuIFBhcmEgZWwgY2FzbyBtdWx0aS1zdGVwLCBhc3VtaW1vcyBxdWUgbG9zIHJlc2lkdW9zIG5vIGVzdMOhbiBhdXRvY29ycmVsYWNpb25hZG9zIHkgc2UgcmVxdWllcmVuIG3DqXRvZG9zIGRlIGPDoWxjdWxvIHVuIHBvY28gbcOhcyBjb21wbGVqb3MuDQoNCiMjIyBNw6l0b2RvcyBkZSByZWZlcmVuY2lhDQoNClNpICRcaGF0e1xzaWdtYX0kIGVzIGxhIGRlc3ZpYWNpw7NuIGVzdMOhbmRhciBkZSBsb3MgcmVzaWR1b3MgeSAkXGhhdHtcc2lnbWF9X3tofSQgZXMgbGEgZGVzdmlhY2nDs24gZXN0w6FuZGFyIGRlbCBwcm9uw7NzdGljbyAkaCQtc3RlcCwgcG9kZW1vcyBjYWxjdWxhciBwYXJhIGNhZGEgbcOpdG9kbyBkZSBwcm9uw7NzdGljbyBkZSByZWZlcmVuY2lhOg0KDQoqKlByb27Ds3N0aWNvcyBkZSBtZWRpYToqKiAkXGhhdHtcc2lnbWF9X3tofT1caGF0e1xzaWdtYX0gXHNxcnR7MSsxIC8gVH0kDQoNCioqUHJvbsOzc3RpY29zIG5hw692ZToqKiAkXGhhdHtcc2lnbWF9X3tofT1caGF0e1xzaWdtYX0gXHNxcnR7aH0kDQoNCioqUHJvbsOzc3RpY29zIG5hw692ZSBlc3RhY2lvbmFsZXM6KiogJFxoYXR7XHNpZ21hfV97aH09XGhhdHtcc2lnbWF9IFxzcXJ0e2srMX0kLCBkb25kZSAkayQgZXMgbGEgcGFydGUgZW50ZXJhIGRlICQoaC0xKS9tJC4NCg0KKipQcm9uw7NzdGljb3MgZGUgZHJpZnQ6KiogJFxoYXR7XHNpZ21hfV97aH09XGhhdHtcc2lnbWF9IFxzcXJ0e2goMStoIC8gVCl9JC4NCg0KVXRpbGl6YW5kbyBsYSBwYXF1ZXRlcsOtYSBgZmFibGVgLCBlcyBtdXkgc2VuY2lsbG8gb2J0ZW5lciBwcm9uw7NzdGljb3MgeSBzdXMgYmFuZGFzLg0KDQpgYGB7ciwgZXZhbD1GQUxTRX0NCmdvb2dsZV8yMDE1ICU+JQ0KICBtb2RlbChOQUlWRShDbG9zZSkpICU+JQ0KICBmb3JlY2FzdChoID0gMTApICU+JQ0KICBoaWxvKCkNCg0KIz4gIyBBIHRzaWJibGU6IDEwIHggNiBbMV0NCiM+ICMgS2V5OiAgICAgICBTeW1ib2wsIC5tb2RlbCBbMV0NCiM+ICAgIFN5bWJvbCAubW9kZWwgICAgICAgICBkYXkgQ2xvc2UgICAgICAgICAgICBgODAlYCAgICAgICAgICAgIGA5NSVgDQojPiAgICA8Y2hyPiAgPGNocj4gICAgICAgIDxkYmw+IDxkYmw+ICAgICAgICAgICA8aGlsbz4gICAgICAgICAgIDxoaWxvPg0KIz4gIDEgR09PRyAgIE5BSVZFKENsb3NlKSAgIDUwNSAgNzU5LiBbNzQ0LjUsIDc3My4yXTgwIFs3MzYuOSwgNzgwLjhdOTUNCiM+ICAyIEdPT0cgICBOQUlWRShDbG9zZSkgICA1MDYgIDc1OS4gWzczOC42LCA3NzkuMl04MCBbNzI3LjksIDc4OS45XTk1DQojPiAgMyBHT09HICAgTkFJVkUoQ2xvc2UpICAgNTA3ICA3NTkuIFs3MzQuMCwgNzgzLjddODAgWzcyMC45LCA3OTYuOV05NQ0KIz4gIDQgR09PRyAgIE5BSVZFKENsb3NlKSAgIDUwOCAgNzU5LiBbNzMwLjIsIDc4Ny42XTgwIFs3MTUuMCwgODAyLjddOTUNCiM+ICA1IEdPT0cgICBOQUlWRShDbG9zZSkgICA1MDkgIDc1OS4gWzcyNi44LCA3OTAuOV04MCBbNzA5LjgsIDgwNy45XTk1DQojPiAgNiBHT09HICAgTkFJVkUoQ2xvc2UpICAgNTEwICA3NTkuIFs3MjMuOCwgNzk0LjBdODAgWzcwNS4yLCA4MTIuNl05NQ0KIz4gIDcgR09PRyAgIE5BSVZFKENsb3NlKSAgIDUxMSAgNzU5LiBbNzIwLjksIDc5Ni44XTgwIFs3MDAuOSwgODE2LjldOTUNCiM+ICA4IEdPT0cgICBOQUlWRShDbG9zZSkgICA1MTIgIDc1OS4gWzcxOC4zLCA3OTkuNF04MCBbNjk2LjgsIDgyMC45XTk1DQojPiAgOSBHT09HICAgTkFJVkUoQ2xvc2UpICAgNTEzICA3NTkuIFs3MTUuOSwgODAxLjldODAgWzY5My4xLCA4MjQuN105NQ0KIz4gMTAgR09PRyAgIE5BSVZFKENsb3NlKSAgIDUxNCAgNzU5LiBbNzEzLjUsIDgwNC4yXTgwIFs2ODkuNSwgODI4LjJdOTUNCmBgYA0KDQpFc3RvIG1pc21vIHNlIHB1ZWRlIGdyYWZpY2FyLCBjb21vIGxvIGhlbW9zIGhlY2hvIGFudGVyaW9ybWVudGU6DQoNCmBgYHtyfQ0KZ29vZ2xlXzIwMTUgJT4lDQogIG1vZGVsKE5BSVZFKENsb3NlKSkgJT4lDQogIGZvcmVjYXN0KGggPSAxMCkgJT4lDQogIGF1dG9wbG90KGdvb2dsZV8yMDE1KQ0KYGBgDQoNCiMjIyBJbnRlcnZhbG9zIGRlIHByZWRpY2Npw7NuIGNvbiByZXNpZHVhbGVzIGJvb3RzdHJhcA0KDQpDdWFuZG8gbm8gZXMgcmF6b25hYmxlIGFzdW1pciAqKm5vcm1hbGlkYWQqKiBlbiBsb3MgcmVzaWR1b3MsIHBvZGVtb3MgYXBsaWNhcmxlcyAqYm9vdHN0cmFwaW5nKiwgeWEgcXVlIGVzdG8gc29sbyBhc3VtZSBsYSBubyBhdXRvY29ycmVsYWNpw7NuLg0KDQpUZW7DrWFtb3MgcXVlIGxvcyByZXNpZHVvcyBzZSBjYWxjdWxhbiAkZV97dH09eV97dH0tXGhhdHt5fV97dCB8IHQtMX0kLiBSZWVzY3JpYmllbmRvLCB0ZW5lbW9zIHF1ZToNCg0KJCR5X3t0fT1caGF0e3l9X3t0IHwgdC0xfStlX3t0fSQkDQp5IHBvZGVtb3Mgc2ltdWxhciBsYSBzaWd1aWVudGUgb2JzZXJ2YWNpw7NuIGRlIHVuYSBzZXJpZSBkZSB0aWVtcG8gY29uOg0KDQokJHlfe1QrMX09XGhhdHt5fV97VCsxIHwgVH0rZV97VCsxfSQkDQogDQpkb25kZSAkXGhhdHt5fV97VCsxfSB8IFQkIGVzIGVsIHByb27Ds3N0aWNvIGRlIHVuIHBlcmlvZG8gKG9uZS1zdGVwKSB5ICRlX3tUKzF9JCBlcyBlbCBlcnJvciBmdXR1cm8gKHF1ZSBkZXNjb25vY2Vtb3MpLiBZYSBxdWUgbm8gZXN0w6FuIGF1dG9jb3JlbGFjaW9uYWRvcyBsb3MgZXJyb3JlcywgeSBwdWVzdG8gcXVlIGFzdW1pbW9zIHF1ZSBsb3MgZXJyb3JlcyBmdXR1cm9zIHNlcsOhbiBzaW1pbGFyZXMgYSBsb3MgaGlzdMOzcmljb3MsIHBvZGVtb3MgY2FtYmlhciAkZV97VCsxfSQgYWwgaGFjZXIgdW4gbXVlc3RyZW8gZGUgbG9zIHJlc2lkdW9zLiBQb2RlbW9zIHJlYWxpemFyIGVsIG1pc21vIHByb2Nlc28gcGFyYSAkeV97VCsyfSwgeV97VCszfSwgXGRvdHMkLg0KDQpTaSByZWFsaXphbW9zIGVzdG8gdmFyaWFzIHZlY2VzLCBvYnRlbmRyZW1vcyBtdWNob3MgZXNjZW5hcmlvcyBmdXR1cm9zIHBvc2libGVzLiBQYXJhIHZlciBhbGd1bm9zIGRlIGVsbG9zLCB1dGlsaXphbW9zIGBnZW5lcmF0ZWAuDQoNCiFbRHIuIFN0cmFuZ2Ugc2ltdWxhbmRvIG1lZGlhbnRlIGJvb3RzdHJhcCAxNCwwMDAsNjA1IGVzY2VuYXJpb3MgcG9zaWJsZXNdKC4uL2ltYWdlcy9kcl9zdHJhbmdlLmpwZWcpDQoNCmBgYHtyIGJvb3RzdHJhcH0NCmZpdCA8LSBnb29nbGVfMjAxNSAlPiUNCiAgbW9kZWwoTkFJVkUoQ2xvc2UpKQ0KDQpzaW0gPC0gZml0ICU+JSAgZ2VuZXJhdGUoaCA9IDMwLCB0aW1lcyA9IDUsIGJvb3RzdHJhcCA9IFRSVUUsIHNlZWQgPSAxMjMpICMgb25seSB3b3JrcyB3aXRoIGRldiB2ZXJzaW9uDQoNCnNpbQ0KYGBgDQoNCg0KDQoNCkxvIHF1ZSBoaWNpbW9zIGZ1ZSBnZW5lcmFyIDUgZXNjZW5hcmlvcyBmdXR1cm9zIHBvc2libGVzIChgdGltZXMgPSA1YCkgcGFyYSBsb3Mgc2lndWllbnRlcyAzMCBkw61hcyBkZSB0cmFkaW5nIChgaCA9IDMwYCkuIFNpIGdyYWZpY2Ftb3MgZXN0bywgdGVuZW1vczoNCg0KYGBge3IgYm9vdHN0cmFwIHBsb3R9DQpnb29nbGVfMjAxNSAlPiUNCiAgZ2dwbG90KGFlcyh4ID0gZGF5KSkgKw0KICBnZW9tX2xpbmUoYWVzKHkgPSBDbG9zZSksIHNpemUgPSAxKSArDQogIGdlb21fbGluZShhZXMoeSA9IC5zaW0sIGNvbG91ciA9IGFzLmZhY3RvcigucmVwKSksIGRhdGEgPSBzaW0sIHNpemUgPSAxKSArDQogIGdndGl0bGUoIkdvb2dsZSBjbG9zaW5nIHN0b2NrIHByaWNlIikgKw0KICBndWlkZXMoY29sID0gRkFMU0UpDQpgYGANCg0KQ29uIGVzdG8sIHBvZGVtb3Mgb2J0ZW5lciBpbnRlcnZhbG9zIGRlIHByZWRpY2Npw7NuLCBhbCBjYWxjdWxhciBsb3MgcGVyY2VudGlsZXMgZGUgbG9zIGVzY2VuYXJpb3MgZnV0dXJvcy4gRWwgcmVzdWx0YWRvIHNlIGxsYW1hICoqaW50ZXJ2YWxvIGRlIHByZWRpY2Npw7NuIGJvb3RzdHJhcHBlZCoqLiBFc3RvIHNlIHB1ZWRlIGxvZ3JhciBmw6FjaWxtZW50ZSBjb24gYGZvcmVjYXN0YC4NCg0KYGBge3J9DQpmYyA8LSBmaXQgJT4lIGZvcmVjYXN0KGggPSAzMCwgYm9vdHN0cmFwID0gVFJVRSkNCmZjDQpgYGANCg0KR3JhZmljYW5kbzoNCg0KDQpgYGB7cn0NCmZjICU+JSBhdXRvcGxvdChnb29nbGVfMjAxNSkgKw0KICBnZ3RpdGxlKCJHb29nbGUgY2xvc2luZyBzdG9jayBwcmljZSIpDQpgYGANCg0KIyBUYXJlYQ0KDQoxLiBDb25zZWd1aXIgZGF0b3MgaGlzdMOzcmljb3Mgc29icmUgZG9zIHNlcmllcyBkZSB0aWVtcG8uDQoyLiBTZWd1aXIgbG9zIHBhc29zIGRlbCBmbHVqbyBkZSB0cmFiYWpvIGRlIHByb27Ds3N0aWNvLg0KMy4gRXN0aW1hciBsb3MgbW9kZWxvcyBkZSByZWZlcmVuY2lhICgqYmVuY2htYXJrKikgcXVlIGNvbnNpZGVyZW4gYWRlY3VhZG9zIHBhcmEgc3Ugc2VyaWUuDQo0LiBSZWFsaWNlbiBlbCBkaWFnbsOzc3RpY28gZGUgcmVzaWR1YWxlcyBlIGludGVycHJldGVuIGxvcyByZXN1bHRhZG9zLg0KNS4gRWplY3V0ZW4gdW4gcHJvbsOzc3RpY28gKHVzdGVkZXMgZGVjaWRlbiBlbCBob3Jpem9udGUgZGUgcHJvbsOzc3RpY28pIHkgZGVmaW5hbiBzaSB1dGlsaXphciBlbCBtw6l0b2RvIGJvb3RzdHJhcCBvIG5vIHkganVzdGlmaXF1ZW4gc3UgZGVjaXNpw7NuLg0KDQoqKioNCg0KIyBQcm9uw7NzdGljb3MgY29uIHRyYW5zZm9ybWFjaW9uZXMNCg0Kwr9RdcOpIHN1Y2VkZSBjdWFuZG8gcmVhbGl6YW1vcyBwcm9uw7NzdGljb3MgZGUgc2VyaWVzIGEgbGFzIHF1ZSBsZXMgaGljaW1vcyBhbGd1bmEgdHJhbnNmb3JtYWNpw7NuPyBQb3IgZWplbXBsbywgc2kgbG9zIGNvbnRyYXRhbiBwYXJhIHByb25vc3RpY2FyIGxhcyB2ZW50YXMgZGUgY3VicmVib2NhcyBlbiBsYSBmYXJtYWNpYSB5IHVzdGVkZXMgaHViaWVyYW4gcmVhbGl6YWRvIHVuYSB0cmFuc2Zvcm1hY2nDs24gbG9nYXLDrXRtaWNhIGRlIGxvcyBkYXRvcywgwr9jw7NtbyBzZSByZWFsaXphIGVsIHByb27Ds3N0aWNvPw0KDQpDdWFuZG8gc2UgcmVhbGl6YSB1bmEgdHJhbnNmb3JtYWNpw7NuIG1hdGVtw6F0aWNhLCBlbCBwcm9uw7NzdGljbyBzZSBoYWNlIGNvbiBlc2Egc2VyaWUgdHJhbnNmb3JtYWRhLCB5IHBvc3Rlcmlvcm1lbnRlIHRlbmVtb3MgcXVlIGRhcmxlICpyZXZlcnNhKiBhIGxhIHRyYW5zZm9ybWFjacOzbiAoKmJhY2stdHJhbnNmb3JtYXRpb24qKSwgcGFyYSBvYnRlbmVyIHByb27Ds3N0aWNvcyBlbiBsYSBlc2NhbGEgb3JpZ2luYWwgZGUgbGEgc2VyaWUuDQoNCkxhIHRyYW5zZm9ybWFjacOzbiBpbnZlcnNhIGRlIEJveC1Db3ggZXN0w6EgZGFkYSBwb3I6DQoNCiQkDQp5X3t0fT1cbGVmdFx7XGJlZ2lue2FycmF5fXtsbH0NClxleHAgXGxlZnQod197dH1ccmlnaHQpICYgXHRleHQgeyBzaSB9IFxsYW1iZGE9MCBcXA0KXGxlZnQoXGxhbWJkYSB3X3t0fSsxXHJpZ2h0KV57MSAvIFxsYW1iZGF9ICYgXHRleHQgeyBlbiBvdHJvIGNhc28gfQ0KXGVuZHthcnJheX1ccmlnaHQuDQokJA0KDQpMYSBwYXF1ZXRlcsOtYSBgZmFibGVgIGNvbnZlbmllbnRlbWVudGUgcmVhbGl6YSBsYSB0cmFuc2Zvcm1hY2nDs24gaW52ZXJzYSBlbiBhdXRvbcOhdGljbywgY3VhbmRvIHNlIGVzcGVjaWZpY2EgZW4gbGEgZGVmaW5pY2nDs24gZGVsIG1vZGVsby4NCg0KIyMjIEludGVydmFsb3MgZGUgcHJlZGljY2nDs24gY29uIHRyYW5zZm9ybWFjaW9uZXMNCg0KRWwgaW50ZXJ2YWxvIGRlIHByZWRpY2Npw7NuIGRlIHVuYSBzZXJpZSB0cmFuc2Zvcm1hZGEgc2UgY2FsY3VsYSwgcHJpbWVyYW1lbnRlLCBlbiBsYSBlc2NhbGEgdHJhbnNmb3JtYWRhLCB5IHBvc3Rlcmlvcm1lbnRlIHNlIGhhY2UgbGEgdHJhbnNmb3JtYWNpw7NuIGludmVyc2EgYSBsYSBlc2NhbGEgb3JpZ2luYWwuIEhhY2VyIGVzdG8gbWFudGllbmUgbG9zIHBvcmNlbnRhamVzIGRlIGNvYmVydHVyYSBkZSBwcm9iYWJpbGlkYWQgb3JpZ2luYWxlcywgcGVybyBlbCByZXN1bHRhZG8geWEgbm8gZXMgc2ltw6l0cmljbyBhbHJlZGVkb3IgZGUgbGEgZXN0aW1hY2nDs24gcHVudHVhbC4NCg0KRXMgaW1wb3J0YW50ZSBtZW5jaW9uYXIgcXVlIGxhcyB0cmFuc2Zvcm1hY2lvbmVzIHRpZW5lbiBtdXkgcG9jbyBlZmVjdG8gZW4gbGEgZXN0aW1hY2nDs24gcHVudHVhbCwgcGVybyBwdWVkZW4gbGxlZ2FyIGEgdGVuZXIgdW4gZ3JhbiBpbXBhY3RvIGVuIGxvcyBpbnRlcnZhbG9zIGRlIHByZWRpY2Npw7NuLg0KDQojIyMgQWp1c3RlcyBwb3Igc2VzZ28NCg0KVW4gcHJvYmxlbWEgYWwgcmVhbGl6YXIgdHJhbnNmb3JtYWNpb25lcyBtYXRlbcOhdGljYXMsIGNvbW8gQm94LUNveCBlcyBxdWUgbGEgZXN0aW1hY2lvbmVzIHB1bnR1YWxlcyByZSB0cmFuc2Zvcm1hZGFzIHlhIG5vIHJlcHJlc2VudGFuIGxhICoqbWVkaWEqKiBkZSBsYSBkaXN0cmlidWNpw7NuIGRlIHByZWRpY2Npw7NuLCBzaW5vIHF1ZSByZXByZXNlbnRhbiBhaG9yYSBsYSAqKm1lZGlhbmEqKi4gRW4gbXVjaG9zIGNhc29zIHB1ZWRlIG5vIHNlciB0YW4gZ3JhdmUgZXN0bywgcGVybyBlbiBvY2FzaW9uZXMgZWwgcHJvbsOzc3RpY28gcHJvbWVkaW8gZXMgcmVxdWVyaWRvOg0KDQoqICpQLiBlai4sIHNpIHF1aWVyZW4gcmVhbGl6YXIgZWwgcHJvbsOzc3RpY28gZGUgbGEgdmVudGEgZGUgY3VicmVib2NhcyBlbiBsYXMgZmFybWFjaWFzIGRlIGxhIFpNRywgcGFyYSwgYWwgc3VtYXJsb3MsIG9idGVuZXIgZWwgcHJvbsOzc3RpY28gZGUgbGFzIHZlbnRhcyB0b3RhbGVzIGRlIGN1YnJlYm9jYXMgZW4gWk1HLiBMYSBzdW1hIGRlIGxhcyBtZWRpYXMgZGEgY29tbyByZXN1bHRhZG8gZWwgdG90YWwsIHBlcm8gbGEgc3VtYSBkZSBtZWRpYW5hcyBuby4qDQoNCkxhIHRyYW5zZm9ybWFjacOzbiBpbnZlcnNhIGRlIGxhIG1lZGlhLCBwYXJhIEJveC1Db3ggZXM6DQoNCiQkDQp5X3t0fT1cbGVmdFx7XGJlZ2lue2FycmF5fXtsbH0NClxleHAgXGxlZnQod197dH1ccmlnaHQpXGxlZnRbMStcZnJhY3tcc2lnbWFfe2h9XnsyfX17Mn1ccmlnaHRdICYgXHRleHQgeyBzaSB9IFxsYW1iZGE9MCBcXA0KXGxlZnQoXGxhbWJkYSB3X3t0fSsxXHJpZ2h0KV57MSAvIFxsYW1iZGF9XGxlZnRbMStcZnJhY3tcc2lnbWFfe2h9XnsyfSgxLVxsYW1iZGEpfXsyXGxlZnQoXGxhbWJkYSB3X3t0fSsxXHJpZ2h0KV57Mn19XHJpZ2h0XSAmIFx0ZXh0IHsgZW4gb3RybyBjYXNvIH0NClxlbmR7YXJyYXl9XHJpZ2h0Lg0KJCQNCmRvbmRlICRcc2lnbWFfe2h9XnsyfSQgZXMgbGEgdmFyaWFuemEgZGVsIHByb27Ds3N0aWNvIGVuIGVsIGhvcml6b250ZS0kaCQgZW4gbGEgZXNjYWxhIHRyYW5zZm9ybWFkYS4gRW50cmUgbcOhcyBncmFuZGUgc2VhIGxhIHZhcmlhbnphLCBtYXlvciBzZXLDoSBsYSBkaWZlcmVuY2lhIGVudHJlIGxhIG1lZGlhIHkgbGEgbWVkaWFuYS4gQSBlc3RvIHNlIGxlIGNvbm9jZSBjb21vIGxhIGVzdGltYWNpw7NuICoqYWp1c3RhZGEgcG9yIHNlc2dvKiouDQoNClZlYW1vcyB1biBlamVtcGxvIHNvYnJlIGVsIHByb27Ds3N0aWNvIGRlbCBwcmVjaW8gcHJvbWVkaW8gZGVsIGh1ZXZvLg0KDQpgYGB7cn0NCmVnZ3MgPC0gYXNfdHNpYmJsZShmbWE6OmVnZ3MpDQpmaXQgPC0gZWdncyAlPiUgbW9kZWwoUlcobG9nKHZhbHVlKSB+IGRyaWZ0KCkpKQ0KZmMgPC0gZml0ICU+JSBmb3JlY2FzdChoPTUwKSAlPiUNCiAgbXV0YXRlKEZvcmVjYXN0ID0gIkJpYXMgYWRqdXN0ZWQiKQ0KZmNfYmlhc2VkIDwtIGZpdCAlPiUgZm9yZWNhc3QoaD01MCwgYmlhc19hZGp1c3QgPSBGQUxTRSkgJT4lDQogIG11dGF0ZShGb3JlY2FzdCA9ICJTaW1wbGUgYmFjayB0cmFuc2Zvcm1hdGlvbiIpDQplZ2dzICU+JSBhdXRvcGxvdCh2YWx1ZSkgKw0KICBhdXRvbGF5ZXIoZmNfYmlhc2VkLCBsZXZlbCA9IDgwKSArDQogIGF1dG9sYXllcihmYywgY29sb3VyID0gInJlZCIsIGxldmVsID0gTlVMTCkNCmBgYA0KDQpMYSBsw61uZWEgYXp1bCByZXByZXNlbnRhIGVsIHByb27Ds3N0aWNvIHNlc2dhZG8sIG1pZW50cmFzIHF1ZSBsYSBsw61uZWEgcm9qYSBtdWVzdHJhIGVsIHByb27Ds3N0aWNvIGNvcnJlZ2lkbyBwb3IgZWwgc2VzZ28uIGBmYWJsZWAgZW4gYXV0b23DoXRpY28gbm9zIHByb2R1Y2UgcHJvbsOzc3RpY29zIGFqdXN0YWRvcyBwb3Igc2VzZ28uIENvbW8gc2UgdmUgZW4gZWwgY8OzZGlnbywgcGFyYSBnZW5lcmFyIHByb27Ds3N0aWNvcyBzZXNnYWRvcywgdGVuZW1vcyBxdWUgZXNwZWNpZmljYXJsbyBtZWRpYW50ZSBgYmlhc19hZGp1c3QgPSBGQUxTRWAuDQoNCiMgUHJvbsOzc3RpY29zIGNvbiBkZXNjb21wb3NpY2nDs24NCg0KTGEgZGVzY29tcG9zaWNpw7NuIGRlIHNlcmllcyBkZSB0aWVtcG8gcHVlZGUgc2VyIMO6dGlsIHBhcmEgcHJvZHVjaXIgcHJvbsOzc3RpY29zLiBSZWVzY3JpYmllbmRvIGxhcyBmw7NybXVsYXMgZGUgZGVzY29tcG9zaWNpw7NuIGFkaXRpdmEgeSBtdWx0aXBsaWNhdGl2YToNCg0KJCR5X3t0fT1caGF0e1N9X3t0fStcaGF0e0F9X3t0fSQkDQoNCmRvbmRlICRcaGF0e0F9X3t0fSQgZXMgbGEgc2VyaWUgZGVzZXN0YWNpb25hbGl6YWRhICggJFxoYXR7QX1fe3R9ID0gXGhhdHtUfV97dH0gKyBcaGF0e1J9X3t0fSQpLg0KDQokJHlfe3R9PVxoYXR7U31fe3R9XGhhdHtBfV97dH0kJA0KDQpjb24gICRcaGF0e0F9X3t0fSA9IFxoYXR7VH1fe3R9ICBcaGF0e1J9X3t0fSQuDQoNCg0KQXPDrSwgZWwgcHJvbsOzc3RpY28gc2UgcmVhbGl6YSBlbiBkb3MgcGFzb3M6IHVuIHByb27Ds3N0aWNvIHBhcmEgZWwgY29tcG9uZW50ZSBlc3RhY2lvbmFsLCB5IHVuIHByb27Ds3N0aWNvIHNlcGFyYWRvIHBhcmEgbGEgc2VyaWUgZGVzZXN0YWNpb25hbGl6YWRhLiBEZSBoZWNobywgZWwgcHJvbsOzc3RpY28gZGVsIGNvbXBvbmVudGUgZXN0YWNpb25hbCBlcyBzaW1wbGVtZW50ZSBlbCBtw6l0b2RvICoqbmHDr3ZlIGVzdGFjaW9uYWwqKi4gUGFyYSBsb3MgZGF0b3MgZGVzZXN0YWNpb25hbGl6YWRvcywgcG9kZW1vcyB1dGlsaXphciBjdWFscXVpZXIgbW9kZWxvIGRlIHByb27Ds3N0aWNvIHF1ZSB2ZXJlbW9zIG3DoXMgYWRlbGFudGUuDQoNCg0KYGBge3J9DQp1c19yZXRhaWxfZW1wbG95bWVudCA8LSB1c19lbXBsb3ltZW50ICU+JQ0KICBmaWx0ZXIoeWVhcihNb250aCkgPj0gMTk5MCwgVGl0bGUgPT0gIlJldGFpbCBUcmFkZSIpDQpkY21wIDwtIHVzX3JldGFpbF9lbXBsb3ltZW50ICU+JQ0KICBtb2RlbChTVEwoRW1wbG95ZWQgfiB0cmVuZCh3aW5kb3cgPSA3KSwgcm9idXN0PVRSVUUpKSAlPiUNCiAgY29tcG9uZW50cygpICU+JQ0KICBzZWxlY3QoLS5tb2RlbCkNCmRjbXAgJT4lDQogIG1vZGVsKE5BSVZFKHNlYXNvbl9hZGp1c3QpKSAlPiUNCiAgZm9yZWNhc3QoKSAlPiUNCiAgYXV0b3Bsb3QoZGNtcCkgKyB5bGFiKCJOZXcgb3JkZXJzIGluZGV4IikgKw0KICBnZ3RpdGxlKCJQcm9uw7NzdGljbyBuYcOvdmUgZGUgbG9zIGRhdG9zIGRlc2VzdGFjaW9uYWxpemFkb3MiKQ0KYGBgDQoNCkEgZXN0YSBzZXJpZSwgcG9kZW1vcyBhZ3JlZ2FybGUgbnVldmFtZW50ZSBsYSBlc3RhY2lvbmFsaWRhZCBjb24gbGEgZnVuY2nDs24gYGRlY29tcG9zaXRpb25fbW9kZWwoKWAuDQoNCg0KYGBge3J9DQp1c19yZXRhaWxfZW1wbG95bWVudCAlPiUNCiAgbW9kZWwoc3RsZiA9IGRlY29tcG9zaXRpb25fbW9kZWwoDQogICAgICAgICAgICAgU1RMKEVtcGxveWVkIH4gdHJlbmQod2luZG93ID0gNyksIHJvYnVzdCA9IFRSVUUpLA0KICAgICAgICAgICAgIE5BSVZFKHNlYXNvbl9hZGp1c3QpDQogICkpICU+JQ0KICBmb3JlY2FzdCgpICU+JQ0KICBhdXRvcGxvdCh1c19yZXRhaWxfZW1wbG95bWVudCkNCmBgYA0KDQojIEV2YWx1YWNpw7NuIGRlbCBkZXNlbXBlw7FvIGRlIGxvcyBwcm9uw7NzdGljb3MNCg0KIyMjIENvbmp1bnRvcyBkZSBlbnRyZW5hbWllbnRvIHkgcHJ1ZWJhDQoNCkNvbW8gaGVtb3MgZGljaG8sIGVzIG11eSBpbXBvcnRhbnRlIHNlcGFyYXIgbnVlc3Ryb3MgZGF0b3MgZW4gZG9zIGNvbmp1bnRvczogdW4gY29uanVudG8gZGUgZGF0b3MgZGUgKiplbnRyZW5hbWllbnRvKiosIHF1ZSBzb24gbG9zIHF1ZSBzZSB1dGlsaXphbiBwYXJhIGVzdGltYXIgZWwgbW9kZWxvLCB5IHVuIGNvbmp1bnRvIGRlIGRhdG9zIGRlICoqcHJ1ZWJhKiosIGRvbmRlIHNlIGV2YWzDumEgZWwgZGVzZW1wZcOxbyBkZWwgcHJvbsOzc3RpY28uDQoNCkVsIHRhbWHDsW8gZGUgbGEgcHJ1ZWJhIGVzLCBnZW5lcmFsbWVudGUsIGRlbCAyMCUgZGVsIHRvdGFsIGRlIGRhdG9zIGRpc3BvbmlibGVzLCBhdW5xdWUgdGFtYmnDqW4gcHVlZGUgZGVwZW5kZXIgZGVsIGhvcml6b250ZSBkZSBwcm9uw7NzdGljbyByZXF1ZXJpZG8uIExhIHBydWViYSB0aWVuZSBxdWUgc2VyIGFsIG1lbm9zIHRhbiBncmFuZGUgY29tbyBlbCBob3Jpem9udGUgZGUgcHJvbsOzc3RpY28gbcOhcyBsYXJnbyBxdWUgc2UgcmVxdWllcmEgKHNpIHNlIG5lY2VzaXRhbiBwcm9uw7NzdGljb3MgcGFyYSB0b2RvIGVsIHNpZ3VpZW50ZSBhw7FvLCBsYSBwcnVlYmEgdGllbmUgcXVlIHNlciBkZWwgdGFtYcOxbyBkZSBhbCBtZW5vcyBkZSB0b2RvIGVsIHNpZ3VpZW50ZSBhw7FvKS4NCg0KKipOT1RBKio6IEVzIGltcG9ydGFudGUgdGVuZXIgZW4gY3VlbnRhIGxvIHNpZ3VpZW50ZToNCg0KKiBVbiBtb2RlbG8gcXVlIHNlIGFqdXN0YSBtdXkgYmllbiBhIGxvcyBkYXRvcyBkZSBlbnRyZW5hbWllbnRvIG5vIG5lY2VzYXJpYW1lbnRlIHByb2R1Y2UgbG9zIG1lam9yZXMgcHJvbsOzc3RpY29zLg0KDQoqIFBvZGVtb3MgbGxlZ2FyIGEgdGVuZXIgdW4gYWp1c3RlIHBlcmZlY3RvIGRlbCBtb2RlbG8gYSBsb3MgZGF0b3MsIHNpIGF1bWVudGFtb3MgbGEgY2FudGlkYWQgZGUgcGFyw6FtZXRyb3MuDQoNCiogUHVlZGUgZGFyc2UgdW4gZWZlY3RvIGRlIHNvYnJlIGFqdXN0ZSAobWVqb3IgY29ub2NpZG8gY29tbyAqb3Zlci1maXR0aW5nKikgeSBlc3RvIGVzIHRhbiBtYWxvIGNvbW8gdGVuZXIgdW4gbXV5IG1hbCBhanVzdGUuDQoNCkVuIGFsZ3VuYSBsaXRlcmF0dXJhIG8gcGFxdWV0ZXLDrWFzIHB1ZWRlbiBlbmNvbnRyYXIgcXVlIGFsIGNvbmp1bnRvIGRlIGRhdG9zIGRlIGVudHJlbmFtaWVudG8gc2UgbGVzIGNvbm96Y2EgY29tbyAiKmluLXNhbXBsZSBkYXRhKiIgeSBhbCBjb25qdW50byBkZSBwcnVlYmEgIipvdXQtb2Ytc2FtcGxlIGRhdGEqIi4NCg0KIyMjIEZ1bmNpb25lcyBwYXJhIHNlZ21lbnRhciBsYXMgc2VyaWVzIGRlIHRpZW1wbw0KDQpIZW1vcyB2aXN0byBxdWUgcG9kZW1vcyB1dGlsaXphciBsYSBmdW5jacOzbiBgZmlsdGVyYCBwYXJhIGZpbHRyYXIgdW5hIGJhc2UgZGUgZGF0b3MgbyB1bmEgc2VyaWUgZGUgdGllbXBvLiANCg0KYGBge3J9DQphdXNfcHJvZHVjdGlvbg0KYGBgDQpQb3IgZWplbXBsbywgdG9tYW5kbyBsYSBwcm9kdWNjacOzbiBlbiBBdXN0cmFsaWEsIHBvZGVtb3MgZmlsdHJhciBwYXJhIHRlbmVyIGxvcyBkYXRvcyBhIHBhcnRpciBkZSAxOTk1Og0KDQpgYGB7cn0NCmF1c19wcm9kdWN0aW9uICU+JSBmaWx0ZXIoeWVhcihRdWFydGVyKSA+PSAxOTk1KQ0KYGBgDQoNCm8gcGFyYSBvYnRlbmVyIGxvcyBkYXRvcyBkZSB1bmEgY2llcnRhIGVzdGFjacOzbjoNCg0KYGBge3J9DQphdXNfcHJvZHVjdGlvbiAlPiUgZmlsdGVyKHF1YXJ0ZXIoUXVhcnRlcikgPT0gMSkNCmBgYA0KDQpPdHJhIGZ1bmNpw7NuIMO6dGlsIHBhcmEgZmlsdHJhciBvIHNlZ21lbnRhciBzZXJpZXMgZGUgdGllbXBvIGVzIGBzbGljZSgpYCwgcXVlIHV0aWxpemEgZWwgw61uZGljZSBwYXJhIGZpbHRyYXIgbG9zIGRhdG9zLg0KDQpgYGB7cn0NCmF1c19wcm9kdWN0aW9uICU+JQ0KICBzbGljZShuKCktMTk6MCkNCmBgYA0KRXN0byBmaWx0cmEgbG9zIMO6bHRpbW9zIDIwIGRhdG9zICg1IGHDsW9zKS4NCg0KQSBwYXJ0aXIgZGUgYGRwbHlyIDEuMC4wYCwgZXhpc3RlbiBhbGd1bmFzIHZhcmlhbnRlcyBkZSBgc2xpY2UoKWAgcXVlIHB1ZWRlbiBzZXJub3MgYmFzdGFudGUgw7p0aWxlcywgY29tbyBgc2xpY2VfaGVhZCgpYCwgYHNsaWNlX3RhaWwoKWAsIGBzbGljZV9zYW1wbGUoKWAsIGBzbGljZV9tYXgoKWAsIGV0Yy4NCg0KUG9kZW1vcyByZWVzY3JpYmlyIGVsIGPDs2RpZ28gZGUgYXJyaWJhIHBhcmEgaGFjZXJsbyBtw6FzIGV4cGzDrWNpdG86DQoNCmBgYHtyfQ0KYXVzX3Byb2R1Y3Rpb24gJT4lIA0KICBzbGljZV90YWlsKG4gPSAyMCkNCmBgYA0KDQpQb2RlbW9zIHVzYXIgYHNsaWNlYCBwYXJhIGRhdG9zIGFncnVwYWRvczoNCg0KYGBge3J9DQphdXNfcmV0YWlsICU+JQ0KICBncm91cF9ieShTdGF0ZSwgSW5kdXN0cnkpICU+JQ0KICBzbGljZSgxOjEyKQ0KYGBgDQpPdHJhIGZ1bmNpw7NuIGVzIGB0b3BfbmAsIGxhIGN1YWwgbm9zIHBlcm1pdGUgb2J0ZW5lciBsYXMgYG5gIG9ic2VydmFjaW9uZXMgbcOhcyBleHRyZW1hcy4NCg0KYGBge3J9DQpnYWZhX3N0b2NrICU+JQ0KICBncm91cF9ieShTeW1ib2wpICU+JQ0KICB0b3BfbigxLCBDbG9zZSkNCmBgYA0KDQojIyBFcnJvcmVzIGRlIHByb27Ds3N0aWNvDQoNCkVsIGVycm9yIGRlIHByb27Ds3N0aWNvIGVzIGxhIGRpZmVyZW5jaWEgZW50cmUgZWwgdmFsb3IgcmVhbCBvY3VycmlkbyB5IGVsIGRhdG8gcHJvbm9zdGljYWRvLiANCg0KJCRlX3tUK2h9PXlfe1QraH0tXGhhdHt5fV97VCtoIHwgVH0kJA0KDQpMb3MgZXJyb3JlcyBkZSBwcm9uw7NzdGljbyBzb24gZGlzdGludG9zIGRlIGxvcyByZXNpZHVhbGVzIGVuIGRvcyBmb3JtYXM6DQoNCjEuIExvcyByZXNpZHVhbGVzIHNlIGNhbGN1bGFuIGNvbiBsb3MgZGF0b3MgZGUgKmVudHJlbmFtaWVudG8qLCBtaWVudHJhcyBxdWUgbG9zIGVycm9yZXMgZGUgcHJvbsOzc3RpY28gc2UgY2FsY3VsYW4gY29uIGxvcyBkZSAqcHJ1ZWJhKi4NCjIuIExvcyByZXNpZHVvcyBzZSBjYWxjdWxhbiBtZWRpYW50ZSBwcm9uw7NzdGljb3MgZGUgdW4gcGFzbyAob25lLXN0ZXApLCBkb25kZSBsb3MgZXJyb3JlcyBkZSBwcm9uw7NzdGljbyBwdWVkZW4gc2VyIG11bHRpLXN0ZXAuDQoNCkhheSBtdWNob3MgdGlwb3MgZGUgY8OhbGN1bG8gZGVsIGVycm9yIGdlbmVyYWwgZGUgdW4gbW9kZWxvIGRlIHByb27Ds3N0aWNvLg0KDQojIyMgRXJyb3JlcyBkZXBlbmRpZW50ZXMgZGUgbGEgZXNjYWxhIGRlIGxvcyBkYXRvcw0KDQpMb3MgZXJyb3JlcyBkZSBwcm9uw7NzdGljbyBlc3TDoW4gbWVkaWRvcyBlbiBsYSBtaXNtYSBlc2NhbGEgZGUgbG9zIGRhdG9zIG9yaWdpbmFsZXMuIEhheSBjaWVydG9zIHRpcG9zIGRlIGVycm9yIHF1ZSBzb2xvIHNlIGJhc2FuIGVuDQokZV97VH0kLCBwb3IgbG8gcXVlIHRhbWJpw6luIGRlcGVuZGVuIGRlIGxhIGVzY2FsYSB5IG5vIHB1ZWRlbiBzZXIgdXRpbGl6YWRvcyBwYXJhIGNvbXBhcmFyIGVsIGRlc2VtcGXDsW8gY29uIG90cmEgc2VyaWUgZGUgdGllbXBvIHF1ZSB0ZW5nYSBvdHJhcyB1bmlkYWRlcy4NCg0KTG9zIGRvcyBtw6FzIHV0aWxpemFkb3MgZW4gZXN0ZSBydWJybyBzb24gZWwgKipNQUUqKiB5IGVsICoqUk1TRSoqLg0KDQokJA0KXGJlZ2lue2FsaWduZWR9DQomXHRleHQgeyBNZWFuIGFic29sdXRlIGVycm9yOiB9IFxtYXRocm17TUFFfT1cb3BlcmF0b3JuYW1le21lYW59XGxlZnQoXGxlZnR8ZV97dH1ccmlnaHR8XHJpZ2h0KVxcDQomXHRleHQgeyBSb290IG1lYW4gc3F1YXJlZCBlcnJvcjogfSBcb3BlcmF0b3JuYW1le1JNU0V9PVxzcXJ0e1xvcGVyYXRvcm5hbWV7bWVhbn1cbGVmdChlX3t0fV57Mn1ccmlnaHQpfQ0KXGVuZHthbGlnbmVkfQ0KJCQNCg0KRWwgTUFFIGVzIG11eSB1dGlsaXphZG8gZGViaWRvIGEgc3UgZmFjaWxpZGFkIGRlIGPDs21wdXRvIHkgZGUgaW50ZXJwcmV0YWNpw7NuLiBVbiBtw6l0b2RvIGRlIHByb27Ds3N0aWNvIHF1ZSBtaW5pbWl6YSBlbCBNQUUgbm9zIGRhcsOhIHByb27Ds3N0aWNvcyBkZSBsYSBtZWRpYW5hIGRlIGxhIGRpc3RyaWJ1Y2nDs24uIFByb27Ds3N0aWNvcyBxdWUgbWluaW1pemFuIGVsIFJNU0Ugb2J0aWVuZW4gcHJvbsOzc3RpY29zIGRlIGxhIG1lZGlhLCBwb3IgbG8gcXVlIGVzdGUgbcOpdG9kbyB0YW1iacOpbiBlcyBtdXkgdXRpbGl6YWRvLCBhIHBlc2FyIGRlIHNlciBtw6FzIHBlc2FkbyBjb21wdXRhY2lvbmFsbWVudGUgeSBjb21wbGljYWRvIGRlIGludGVycHJldGFyLg0KDQojIyMgRXJyb3JlcyBwb3JjZW50dWFsZXMNCg0KTG9zIGVycm9yZXMgcG9yY2VudHVhbGVzLCBhbCBzZXIgdW4gcG9yY2VudGFqZSwgbm8gdGllbmVuIHVuaWRhZGVzIHkgc29uIHV0aWxpemFkb3MgcGFyYSBjb21wYXJhciBlbCBkZXNlbXBlw7FvIGRlIHByb27Ds3N0aWNvcyBkZSBkaXN0aW50b3MgY29uanVudG9zIGRlIGRhdG9zLiBFbCBtw6FzIHV0aWxpemFkbyBlcyBlbCBNQVBFIChlcnJvciBhYnNvbHV0byBwcm9tZWRpbyBwb3JjZW50dWFsKS4gU2kgZGVmaW5pbW9zIGFsIGVycm9yIHBvcmNlbnR1YWwgY29tbyAkcF97dH09MTAwIGVfe3R9IC8geV97dH0kDQoNCk1lYW4gYWJzb2x1dGUgcGVyY2VudGFnZSBlcnJvcjogKipNQVBFKiogJD1cb3BlcmF0b3JuYW1le21lYW59XGxlZnQoXGxlZnR8cF97dH1ccmlnaHR8XHJpZ2h0KSQNCg0KTGEgZGVzdmVudGFqYSBjb24gZXN0b3MgZXJyb3JlcyBlcyBxdWUgc2UgaW5kZXRlcm1pbmFuYSBvIHZ1ZWx2ZW4gaW5maW5pdG9zIGNvbiB2YWxvcmVzIGRlICR5X3QgPSAwJC4gUGFyYSBlbGxvLCBzZSBkZWZpbmnDsyBlbCBNQVBFIHNpbcOpdHJpY286DQoNCiQkXG9wZXJhdG9ybmFtZXtzTUFQRX09XG9wZXJhdG9ybmFtZXttZWFufVxsZWZ0KDIwMFxsZWZ0fHlfe3R9LVxoYXR7eX1fe3R9XHJpZ2h0fCAvXGxlZnQoeV97dH0rXGhhdHt5fV97dH1ccmlnaHQpXHJpZ2h0KSQkDQoNCkF1bnF1ZSBlc3RlIG3DqXRvZG8gbm8gc2UgcmVjb21pZW5kYSB0YW50byB1dGlsaXphcmxvIGVuIGxhIHByw6FjdGljYS4NCg0KDQpgYGB7cn0NCnJlY2VudF9wcm9kdWN0aW9uIDwtIGF1c19wcm9kdWN0aW9uICU+JSBmaWx0ZXIoeWVhcihRdWFydGVyKSA+PSAxOTkyKQ0KYmVlcl90cmFpbiA8LSByZWNlbnRfcHJvZHVjdGlvbiAlPiUgZmlsdGVyKHllYXIoUXVhcnRlcikgPD0gMjAwNykNCg0KYmVlcl9maXQgPC0gYmVlcl90cmFpbiAlPiUNCiAgbW9kZWwoDQogICAgTWVhbiA9IE1FQU4oQmVlciksDQogICAgYE5hw692ZWAgPSBOQUlWRShCZWVyKSwNCiAgICBgU2Vhc29uYWwgbmHDr3ZlYCA9IFNOQUlWRShCZWVyKSwNCiAgICBEcmlmdCA9IFJXKEJlZXIgfiBkcmlmdCgpKQ0KICApDQoNCmJlZXJfZmMgPC0gYmVlcl9maXQgJT4lDQogIGZvcmVjYXN0KGggPSAxMCkNCg0KYmVlcl9mYyAlPiUNCiAgYXV0b3Bsb3QocmVjZW50X3Byb2R1Y3Rpb24sIGxldmVsID0gTlVMTCkgKw0KICB4bGFiKCJZZWFyIikgKyB5bGFiKCJNZWdhbGl0cmVzIikgKw0KICBnZ3RpdGxlKCJGb3JlY2FzdHMgZm9yIHF1YXJ0ZXJseSBiZWVyIHByb2R1Y3Rpb24iKSArDQogIGd1aWRlcyhjb2xvdXI9Z3VpZGVfbGVnZW5kKHRpdGxlPSJGb3JlY2FzdCIpKQ0KYGBgDQoNCmBgYHtyIG1vZGVsIGFjY3VyYWN5fQ0KYWNjdXJhY3koYmVlcl9maXQpDQpgYGANCg0KYGBge3IgZm9yZWNhc3QgYWNjdXJhY3l9DQpiZWVyX2ZjICU+JSANCiAgYWNjdXJhY3kocmVjZW50X3Byb2R1Y3Rpb24pDQpgYGANCg0KDQo=